Skip to content

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.

Your App Kotauth User's Browser
│ │ │
│──── 1. Redirect to /auth ──────────>│
│ │<─── 2. Login ──────│
│ │──── 3. Redirect ──>│
│<─── 4. code ───────────────────────│
│──── 5. Exchange code ──>│
│<─── 6. access + refresh tokens ────│
  1. Generate PKCE values

    Before redirecting, generate a code_verifier (a random 43–128 character string) and its code_challenge (SHA-256 hash of the verifier, base64url-encoded):

    const codeVerifier = generateRandomString(64);
    const codeChallenge = base64url(sha256(codeVerifier));

    Store the code_verifier in session storage — you’ll need it in step 5.

  2. 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=S256
    ParameterRequiredDescription
    response_typeYesMust be code
    client_idYesYour application’s client ID
    redirect_uriYesMust exactly match a registered redirect URI
    scopeYesSpace-separated. Include openid for OIDC
    stateRecommendedOpaque 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
  3. 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.

  4. Receive the authorization code

    Kotauth redirects back to your redirect_uri with a short-lived authorization code:

    https://yourapp.com/callback?code=AUTH_CODE&state=YOUR_STATE

    Verify the state value matches what you sent in step 2.

  5. Exchange the code for tokens

    POST /t/{slug}/protocol/openid-connect/token
    Content-Type: application/x-www-form-urlencoded
    grant_type=authorization_code
    &code=AUTH_CODE
    &redirect_uri=https://yourapp.com/callback
    &client_id=YOUR_CLIENT_ID
    &code_verifier=YOUR_CODE_VERIFIER

    For confidential clients, include client_secret as well (or use HTTP Basic auth).

  6. Receive tokens

    {
    "access_token": "eyJ...",
    "token_type": "Bearer",
    "expires_in": 300,
    "refresh_token": "eyJ...",
    "id_token": "eyJ...",
    "scope": "openid profile email"
    }

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/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=CURRENT_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID

To end the session:

GET /t/{slug}/protocol/openid-connect/logout
?id_token_hint=ID_TOKEN
&post_logout_redirect_uri=https://yourapp.com/logged-out

Kotauth revokes the session and redirects to post_logout_redirect_uri.

ScopeClaims included
openidsub, iss, aud, exp, iat
profilename, preferred_username
emailemail, email_verified
rolesrealm_access, resource_access