Web App with Authorization Code and PKCE
1. Discover configuration
const issuer = 'https://account.example.com'
const configuration = await fetch(
`${issuer}/.well-known/openid-configuration`,
).then((response) => response.json())
Cache discovery metadata for a bounded period and refresh it normally. Do not hardcode a JWKS key.
2. Create state, nonce, and PKCE
Generate cryptographically random state, nonce, and code_verifier values. Store them in a secure, short-lived transaction bound to the browser session. Derive code_challenge as base64url-encoded SHA-256 of the verifier.
3. Redirect to authorization
const url = new URL(configuration.authorization_endpoint)
url.search = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'https://app.example.com/api/auth/callback',
response_type: 'code',
scope: 'openid profile email offline_access',
state,
nonce,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
ui_locales: 'fa en',
ui_theme: 'dark',
}).toString()
window.location.assign(url)
The hosted Raha Account pages own login, signup, MFA, account selection, and consent.
4. Handle the callback
Reject the callback if state does not exactly match the stored transaction. Exchange the code from your backend:
curl -X POST "$TOKEN_ENDPOINT" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'code=RETURNED_CODE' \
--data-urlencode 'redirect_uri=https://app.example.com/api/auth/callback' \
--data-urlencode 'code_verifier=ORIGINAL_CODE_VERIFIER'
Validate the ID token signature and claims, including iss, aud, exp, and nonce. Keep refresh tokens and confidential-client secrets on the server.
5. End the session
Use the discovery document's end-session endpoint with id_token_hint, an allow-listed post_logout_redirect_uri, and optional state. Raha Account supports front-channel and back-channel client logout notifications where configured.