loading
Preparing LoginRadius developer resources
Mission: Help enterprises accelerate digital transformation with our fully-managed Customer IAM technology.
Skip to main content

PKCE (Proof Key for Code Exchange)

PKCE (pronounced "pixy") is an extension to the Authorization Code Flow designed for clients that cannot securely store a client_secret — primarily native mobile apps and single-page applications. Instead of a shared secret, the client generates a one-time cryptographic challenge pair (code_verifier and code_challenge) for each authorization request. LoginRadius verifies this pair during the token exchange, ensuring the token request originates from the same client that initiated the authorization.

PKCE is recommended for all public clients and is required when client_secret is not used.

How PKCE Works

  1. Before redirecting the user, the client generates a cryptographically random code_verifier.
  2. The client derives a code_challenge from it using SHA-256: BASE64URL(SHA256(code_verifier)).
  3. The code_challenge and code_challenge_method are sent in the authorization request.
  4. LoginRadius stores the code_challenge tied to the issued authorization_code.
  5. During token exchange, the client sends the original code_verifier instead of a client_secret.
  6. LoginRadius hashes the code_verifier and compares it against the stored code_challenge. If they match, the token is issued.

PKCE Flow Diagram


PKCE Step-by-Step Walkthrough

Step 1 — Generate Code Verifier & Challenge

Before redirecting the user, the client generates a cryptographically random code_verifier and derives a code_challenge from it. Both are generated fresh for every authorization request.

The code_verifier must be a cryptographically random string between 43 and 128 characters, using only unreserved URI characters (A-Z, a-z, 0-9, -, ., _, ~). The code_challenge is derived by hashing the code_verifier with SHA-256 and encoding it as a Base64URL string.

// JavaScript / Node.js
const crypto = require('crypto');

const codeVerifier = crypto.randomBytes(64).toString('base64url');

const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');

Step 2 — Authorization Request

The client redirects the user to the LoginRadius OIDC Authorization endpoint, including the code_challenge and code_challenge_method alongside the standard parameters.

Endpoint

GET https://{SiteURL}/service/oidc/{OIDCAppName}/authorize

SiteURL = Either <TenantName>.hub.loginradius.com or CustomDomain i.e. auth.your-app.com · OIDCAppName = your configured OIDC App name

Example Request

GET https://your-app.hub.loginradius.com/service/oidc/MyApp/authorize
?client_id=YOUR_OIDC_CLIENT_ID
&response_type=code
&scope=openid%20profile%20email
&redirect_uri=https://your-app.com/callback
&state=abc123xyz
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
ParameterValueDescription
code_challengeBASE64URL(SHA256(code_verifier))The derived challenge sent with the authorization request
code_challenge_methodS256Hash algorithm used. S256 (SHA-256) is required. plain is supported but not recommended.

Step 3 — Session Check

Upon receiving the request, LoginRadius checks whether the user already has an active authenticated session.

  • If an active session is found → LoginRadius skips the login UI and immediately redirects to redirect_uri with the authorization code.
  • If no active session → LoginRadius renders the login UI for the user to authenticate.

Step 4 — User Authentication

The user authenticates via the login page. Two login paths are supported:

  • Traditional Login — The user submits their credentials (email/password or phone).
  • Social Login — The user authenticates via a configured social provider. After successful authentication, LoginRadius stores the code_challenge, generates a one-time, short-lived authorization_code, and redirects back to the client's redirect_uri.

Step 5 — Authorization Code Issuance

LoginRadius redirects to the client callback with the authorization code appended as a query parameter.

Success Response

HTTP 302
Location: https://your-app.com/callback?code={authorization_code}&state=abc123xyz

Error Response

HTTP 302
Location: https://your-app.com/callback?error=access_denied&error_description=...

Errors are returned as query parameters on the redirect_uri by default. This behavior can be changed by passing the response_mode parameter in the authorization request.


Step 6 — Token Exchange

The client sends the authorization_code along with the original code_verifier to the token endpoint. The client_secret is not required and must be omitted.

Endpoint

POST https://{SiteURL}/api/oidc/{OIDCAppName}/token

The token endpoint accepts two content types:

Content-TypeDescription
application/x-www-form-urlencodedParameters sent as a URL-encoded form body
application/jsonParameters sent as a JSON object in the request body
POST /api/oidc/{OIDCAppName}/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={authorization_code}
&client_id=YOUR_OIDC_CLIENT_ID
&code_verifier=YOUR_CODE_VERIFIER_STRING
&redirect_uri=https://your-app.com/callback

LoginRadius hashes the code_verifier using SHA-256 and compares it against the code_challenge stored during the authorization step. If they match, the token is issued.

Success Response

{
"access_token": "<LoginRadius JWT Access Token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<Refresh Token>",
"id_token": "<JWT ID Token>"
}

Standard vs PKCE Flow — Key Differences

Standard Authorization Code FlowAuthorization Code Flow with PKCE
Client typeConfidential (server-side)Public (mobile, SPA)
Secret usedclient_secretcode_verifier / code_challenge
Secret storageStored securely on the serverNever stored — generated per request
Token request authclient_secret_post or client_secret_basiccode_verifier in request body
Replay protectionClient secret + short-lived codeOne-time code_verifier challenge pair