Authorization Code + PKCE
import { Aside, Steps } from ‘@astrojs/starlight/components’;
The Authorization Code flow with PKCE (Proof Key for Code Exchange) is the correct flow for virtually all user-facing applications. It keeps tokens out of the browser URL and protects against authorization code interception attacks.
Flow overview
Section titled “Flow overview”Your App Kotauth User's Browser │ │ │ │──── 1. Redirect to /auth ──────────>│ │ │<─── 2. Login ──────│ │ │──── 3. Redirect ──>│ │<─── 4. code ───────────────────────│ │──── 5. Exchange code ──>│ │<─── 6. access + refresh tokens ────│Step by step
Section titled “Step by step”-
Generate PKCE values
Before redirecting, generate a
code_verifier(a random 43–128 character string) and itscode_challenge(SHA-256 hash of the verifier, base64url-encoded):const codeVerifier = generateRandomString(64);const codeChallenge = base64url(sha256(codeVerifier));Store the
code_verifierin session storage — you’ll need it in step 5. -
Redirect to the authorization endpoint
GET /t/{slug}/protocol/openid-connect/auth?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback&scope=openid profile email&state=RANDOM_STATE_VALUE&code_challenge=CODE_CHALLENGE&code_challenge_method=S256Parameter Required Description response_typeYes Must be codeclient_idYes Your application’s client ID redirect_uriYes Must exactly match a registered redirect URI scopeYes Space-separated. Include openidfor OIDCstateRecommended Opaque value to prevent CSRF; verify it on return code_challengeYes (public clients) SHA-256 of code verifier, base64url-encoded code_challenge_methodYes (public clients) Must be S256 -
User authenticates
Kotauth presents the login page. The user enters credentials (and completes MFA if required). This step is entirely handled by Kotauth — your app is not involved.
-
Receive the authorization code
Kotauth redirects back to your
redirect_uriwith a short-lived authorization code:https://yourapp.com/callback?code=AUTH_CODE&state=YOUR_STATEVerify the
statevalue matches what you sent in step 2. -
Exchange the code for tokens
POST /t/{slug}/protocol/openid-connect/tokenContent-Type: application/x-www-form-urlencodedgrant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://yourapp.com/callback&client_id=YOUR_CLIENT_ID&code_verifier=YOUR_CODE_VERIFIERFor confidential clients, include
client_secretas well (or use HTTP Basic auth). -
Receive tokens
{"access_token": "eyJ...","token_type": "Bearer","expires_in": 300,"refresh_token": "eyJ...","id_token": "eyJ...","scope": "openid profile email"}
Refreshing tokens
Section titled “Refreshing tokens”Access tokens are short-lived (default: 5 minutes). When one expires, use the refresh token to get a new pair:
POST /t/{slug}/protocol/openid-connect/tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=CURRENT_REFRESH_TOKEN&client_id=YOUR_CLIENT_IDLogging out
Section titled “Logging out”To end the session:
GET /t/{slug}/protocol/openid-connect/logout ?id_token_hint=ID_TOKEN &post_logout_redirect_uri=https://yourapp.com/logged-outKotauth revokes the session and redirects to post_logout_redirect_uri.
Scopes
Section titled “Scopes”| Scope | Claims included |
|---|---|
openid | sub, iss, aud, exp, iat |
profile | name, preferred_username |
email | email, email_verified |
roles | realm_access, resource_access |