# OAuth Token Exchange and Refresh After the web login flow has set the `blaze_identity` cookie (profile activated), the client exchanges a PKCE authorization code for an `access_token` and `refresh_token`. These tokens are used for all subsequent REST API calls. --- ## Prerequisites - Completed [Login](01-login.md) (and [Business OTP](02-business-otp.md) if applicable) - `blaze_identity` cookie set in the session - PKCE `code_verifier` and `code_challenge` generated at the start of the login session - `Device-ID` generated at the start of the login session --- ## PKCE Parameter Generation Generate these once at the start of each login session: | Parameter | Generation | |---|---| | `code_verifier` | 72 cryptographically random bytes, base64url-encoded (no padding) | | `code_challenge` | SHA-256 hash of `code_verifier` (as ASCII bytes), base64url-encoded (no padding) | | `Device-ID` | 8 cryptographically random bytes, hex-encoded (16 hex chars) | | `state` | 16 random bytes, base64url-encoded | | `nonce` | 12 random bytes, base64url-encoded | --- ## Step 1 — Authorize (Get Auth Code) ``` GET https://www.bankofmaldives.com.mv/internetbanking/oauth/authorize ``` ### Query Parameters | Parameter | Value | |---|---| | `redirect_uri` | `https://app.bankofmaldives.com.mv/oauth/mobile-callback` | | `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | | `response_type` | `code` | | `state` | Random base64url string (16 bytes) | | `nonce` | Random base64url string (12 bytes) | | `code_challenge` | SHA-256 of `code_verifier`, base64url-encoded | | `code_challenge_method` | `S256` | | `Device-ID` | Random 16-char hex string | | `User-Agent` | App user agent string | | `x-app-version` | `2.1.44.348` | ```bash curl --request GET \ --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/authorize?redirect_uri=https%3A%2F%2Fapp.bankofmaldives.com.mv%2Foauth%2Fmobile-callback&client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7&response_type=code&state=&nonce=&code_challenge=&code_challenge_method=S256&Device-ID=&User-Agent=bml-mobile-banking%2F348+%28{manufacturer}%3B+Android+14%3B+{model}%29&x-app-version=2.1.44.348' \ --header 'User-Agent: Mozilla/5.0 (Android {version}; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ --cookie 'blaze_session=; blaze_identity=' ``` ### Response `302` redirect to: ``` https://app.bankofmaldives.com.mv/oauth/mobile-callback?code=&state= ``` Extract the `code` query parameter from the `Location` header. This is the one-time authorization code. --- ## Step 2 — Token Exchange ``` POST https://www.bankofmaldives.com.mv/internetbanking/oauth/token ``` ### Request **Content-Type:** `application/x-www-form-urlencoded` | Field | Value | |---|---| | `grant_type` | `authorization_code` | | `code` | Auth code from Step 1 | | `code_verifier` | PKCE verifier generated at login start | | `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | | `redirect_uri` | `https://app.bankofmaldives.com.mv/oauth/mobile-callback` | | `Device-ID` | Same device ID used in Step 1 | | `User-Agent` | App user agent string | | `x-app-version` | `2.1.44.348` | ```bash curl --request POST \ --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/token' \ --header 'User-Agent: Mozilla/5.0 (Android {version}; Mobile; rv:150.0) Gecko/150.0 Firefox/150.0' \ --data 'grant_type=authorization_code' \ --data 'code=' \ --data 'code_verifier=' \ --data 'client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7' \ --data 'redirect_uri=https%3A%2F%2Fapp.bankofmaldives.com.mv%2Foauth%2Fmobile-callback' \ --data 'Device-ID=' \ --data 'User-Agent=bml-mobile-banking%2F348+%28{manufacturer}%3B+Android+14%3B+{model}%29' \ --data 'x-app-version=2.1.44.348' ``` ### Response ```json { "access_token": "eyJhbGciOiJSUzI1NiJ9...", "refresh_token": "def50200aabbcc...", "token_type": "Bearer", "expires_in": 3600 } ``` | Field | Type | Description | |---|---|---| | `access_token` | `string` | JWT Bearer token — use in `Authorization: Bearer ` on all REST API calls | | `refresh_token` | `string` | Long-lived token — use to refresh without re-login | | `token_type` | `string` | Always `"Bearer"` | | `expires_in` | `number` | Token lifetime in seconds (typically `3600`) | Store `access_token`, `refresh_token`, `expires_in`, and `Device-ID` together as the session. The `access_token` expires at `now + expires_in * 1000` milliseconds. --- ## Token Refresh When the access token expires (on `401` or `419` from any REST endpoint), obtain a new one using the refresh token. No web session or cookies are needed. ``` POST https://www.bankofmaldives.com.mv/internetbanking/oauth/token ``` ### Request **Content-Type:** `application/x-www-form-urlencoded` | Field | Value | |---|---| | `grant_type` | `refresh_token` | | `refresh_token` | Stored refresh token | | `client_id` | `98C83590-513F-4716-B02B-EC68B7D9E7E7` | | `Device-ID` | Same device ID from the original login | | `User-Agent` | App user agent string | | `x-app-version` | `2.1.44.348` | ```bash curl --request POST \ --url 'https://www.bankofmaldives.com.mv/internetbanking/oauth/token' \ --header 'User-Agent: bml-mobile-banking/348 ({manufacturer}; Android {version}; {model})' \ --data 'grant_type=refresh_token' \ --data 'refresh_token=def50200aabbcc...' \ --data 'client_id=98C83590-513F-4716-B02B-EC68B7D9E7E7' \ --data 'Device-ID=a1b2c3d4e5f60718' \ --data 'User-Agent=bml-mobile-banking%2F348+%28{manufacturer}%3B+Android+14%3B+{model}%29' \ --data 'x-app-version=2.1.44.348' ``` ### Response Same structure as the token exchange response. The server may issue a new `refresh_token` (rotating tokens). If a new one is returned, replace the stored value; otherwise keep the original. ```json { "access_token": "eyJhbGciOiJSUzI1NiJ9...", "refresh_token": "def50200...", "token_type": "Bearer", "expires_in": 3600 } ``` **Failure:** If `access_token` is absent or blank in the response, the refresh token has expired. Re-run the full [login flow](01-login.md). --- ## Session Storage Persist the following to represent a saved BML session: | Field | Description | |---|---| | `access_token` | Bearer token for REST API calls | | `refresh_token` | Used to renew without re-login | | `expires_at` | Unix timestamp (ms) when `access_token` expires | | `device_id` | Must be sent with every refresh; ties tokens to the device | ---   --- [← Business Profile OTP](02-business-otp.md)     **Next →** [Dashboard](04-dashboard.md)