Skip to main content

Authorization Code Flow with PKCE

Muhajir Studio supports OpenID Connect through Authorization Code Flow with PKCE. Admin-created applications are Public PKCE clients: they use a public client_id, do not have a client_secret, and must prove possession of a code_verifier during token exchange.

Base URLs Used in This Flow

ServiceBase URLRole in this flow
APIhttps://api.muhajirstudio.com//api/auth/oauth2/authorize, /api/auth/oauth2/token, UserInfo, JWKS, and public metadata.
Main webhttps://login.muhajirstudio.com/Login, signup, email verification, and consent UI.
Admin webhttps://admin.muhajirstudio.com/Client registration and scope/redirect configuration.

Provider Requirements

The OIDC provider is configured with:

  • requirePKCE: true
  • allowPlainCodeChallengeMethod: false
  • defaultScope: openid
  • supported scopes from OIDC_SCOPES
  • JWT-backed OIDC support
  • issuer from OIDC_ISSUER

Only code_challenge_method=S256 is supported. Do not use plain.

1. Prepare Client Configuration

Ask an admin for the values registered in web-admin:

ValueNotes
client_idGenerated public client identifier.
Redirect URIMust exactly match one derived registered redirectUri.
Allowed scopesThe application scope set selected by the admin.
IssuerThe configured OIDC_ISSUER value.

Admin-created applications never receive a client_secret.

2. Generate Per-Request Values

For every login attempt, generate and store:

ValuePurpose
code_verifierSecret random string kept by the client until token exchange.
code_challengeBase64url SHA-256 digest of code_verifier.
stateCSRF protection value that must round-trip.
nonceReplay protection value to validate in the id_token.

Store state, nonce, and code_verifier in a server session or another storage mechanism appropriate for your application type.

3. Start Authorization

Redirect the browser to https://api.muhajirstudio.com/api/auth/oauth2/authorize:

GET /api/auth/oauth2/authorize

Example query:

client_id=CLIENT_ID
redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback
response_type=code
scope=openid%20profile%20email
state=STATE
nonce=NONCE
code_challenge=CODE_CHALLENGE
code_challenge_method=S256

Required protocol parameters:

ParameterRequirement
client_idPublic client ID registered in web-admin.
redirect_uriExact registered full URI.
response_typeMust be code.
scopeSpace-separated scopes; defaults to openid when omitted by provider behavior.
code_challengePKCE S256 challenge.
code_challenge_methodMust be S256.

Recommended parameters:

ParameterRequirement
stateStrongly recommended for CSRF protection.
nonceStrongly recommended when using or validating the id_token.

web-main preserves these OIDC parameters through login, signup, email verification, and the email-verified screen: client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method, nonce, prompt, max_age, and login_hint.

4. Authorization Request Processing

Before the OIDC provider handles the authorize request, Muhajir Studio applies three server-side checks.

Redirect URI Validation

For dynamic applications, the server loads the application by client_id and validates that redirect_uri is in the application's derived redirectUris list.

Static trusted clients, such as the reserved web-admin client, are validated against their configured redirect URLs.

If validation fails, the response is:

{
"error": "INVALID_REDIRECT_URI",
"message": "redirect_uri is not registered for this client"
}

Scope Constraint

For dynamic applications with configured scopes, the server rewrites the requested scope to the application's registered scope set before the OIDC provider processes the request.

This means the admin-selected scopes are the source of truth for dynamic applications.

Email Verification Gate

If the user is signed in but emailVerified is not true, the server redirects to /verify-email on web-main and preserves the original authorize query parameters. The user cannot complete authorization until email verification is done.

The OIDC provider uses configured loginPage and consentPage URLs. The user-facing flow can include:

  1. Login or signup.
  2. Email verification.
  3. Consent.
  4. Redirect back to the client callback.

The consent page loads public application metadata from GET /api/public/applications/{clientId}. When a redirect_uri query value exists, that value is forwarded so the metadata endpoint can safely derive any post-verification redirect URI.

When a signed-in user chooses to switch accounts from the consent page, web-main signs out the current session and returns to the registered application's post-verification URL when one is available. The client application should then start a fresh authorization request with a new state and PKCE challenge.

Approval is disabled when application metadata cannot be loaded.

6. Handle the Callback

After approval, Muhajir Studio redirects to the registered redirect_uri with an authorization code and the original state.

Your callback handler must:

  1. Verify state matches the stored value.
  2. Retrieve the stored code_verifier.
  3. Exchange the code at the token endpoint.
  4. Validate id_token claims when using the ID token for local sign-in.

7. Exchange the Authorization Code

Send a form-encoded token request:

POST /api/auth/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=AUTHORIZATION_CODE&code_verifier=CODE_VERIFIER&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback

Do not send client_secret for admin-created Public PKCE clients.

Successful token responses can include:

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

Use the access token as Authorization: Bearer ACCESS_TOKEN when calling OIDC UserInfo or /api/external/* APIs.

8. Refresh Tokens

The token endpoint also supports grant_type=refresh_token where supported by the OIDC provider and granted scopes/client configuration. The offline_access scope is intended for refresh-token based sessions where supported.

Third-party clients should use the standard token endpoint directly unless explicitly instructed to use a first-party cookie helper.

Security Checklist

  • Use HTTPS redirect URIs outside local development.
  • Generate new state, nonce, and PKCE values for every login attempt.
  • Validate state before exchanging the code.
  • Validate id_token issuer, audience, expiry, signature, and nonce when using it for sign-in.
  • Never use or store a client secret for admin-created Public PKCE clients.
  • Store tokens according to your app type and threat model.