Skip to main content

PKCE Flow

The Authorization Code Flow with PKCE (Proof Key for Code Exchange) is an extension of the Authorization Code Flow. It is specifically designed to authenticate native or mobile application users, but is recommended for all scenarios where user authentication is required, especially for public clients that cannot securely store a client_secret.

PKCE adds an extra layer of security by using a dynamically created cryptographic challenge instead of a static client secret. This makes it ideal for Single Page Applications (SPAs), mobile apps, and other public clients where storing secrets is not secure.

The primary difference between the PKCE flow and the standard Authorization Code flow is that PKCE doesn't require a client_secret. PKCE reduces security risks for native apps, as embedded secrets aren't required in the source code. This minimizes the exposure to reverse engineering security threats.

How it Works

pkce-flow

Before we step into how it works, let's understand some useful parameters.

Parameter NameDescription
client_idclient which represents the business-App in cidaas, where the Resource Owner wants to have access
redirect_uriURI where cidaas should redirect to, after login. Must be provided in both authorization request and token exchange request, and must match in both calls
scopescope which the user asks for. E.g. profile gives access to profile data
response_typetype of response. In PKCE Flow it has to be code
staterandom onetime string which will be generated at the beginning of the flow. Recommended to avoid CSRF
code_challengeA base64url-encoded SHA256 hash of the code_verifier. Sent in the authorization request
code_challenge_methodThe method used to generate the code challenge. Must be S256 for SHA256
grant_typeauthorization_code needs to be mentioned in code-exchange call to get a token
codethe one-time-use code must be provided as code parameter
code_verifierThe code verifier is a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -._~, between 43 and 128 characters long. Must be sent in the token exchange request

Step 1: The Client (your app) creates the code_verifier. (RFC 7636, Section 4.1)

Step 2: Client creates the code_challenge by transforming the code_verifier using SHA256 hashing and base64url encoding. (RFC 7636, Section 4.2)

Step 3: Client sends the code_challenge and code_challenge_method with the initial authorization request. The authorization request includes: response_type=code, redirect_uri (your redirect URI), client_id (the client_id of your application), scope (requested permissions), state (a random string for CSRF protection), code_challenge, and code_challenge_method=S256. (RFC 7636, Section 4.3)

The Authorization Endpoint initiates the login process. Understanding how to construct the authorization URL is crucial for implementing PKCE.

Authz Parts

Step 4: The user will then enter their credentials on the login page and click submit. This step involves user authentication, which can be performed using password credentials or passwordless authentication methods such as Magic Link, Email/SMS OTP, or FIDO2.

Step 5: After successful authentication, cidaas will return a code to the provided redirect_uri in the Authz-Call. The client must verify whether the state returned by the authorization server (cidaas) is equal to the state passed in Step 3. (RFC 7636, Section 4.4)

Step 6: The Client then requests Access Token from Authorization Server. The client sends the one-time-use code as code, code_verifier, grant_type=authorization_code, client_id, and redirect_uri (must match the redirect_uri from Step 3 for security validation) to the token endpoint. Since PKCE is designed for public clients, this request can be made directly from the client application (no backend required). (RFC 7636, Section 4.5)

Step 7: cidaas transforms the code_verifier using the code_challenge_method from the initial authorization request and checks the result against the code_challenge. If the value of both strings match, then the server has verified that the requests came from the same client and will issue an access_token and optionally a refresh token. (RFC 7636, Section 4.6)

Security Note

Important: PKCE is specifically designed for public clients (SPAs, mobile apps) where you cannot securely store a client_secret. The code_verifier is generated client-side and never stored on a server. This flow provides protection against authorization code interception attacks without requiring a client secret.

When to Use PKCE Flow

Use this flow when:

  • You're building a Single Page Application (SPA)
  • You're developing a mobile application (iOS, Android)
  • You cannot securely store a client_secret (public client)
  • You want the highest security for public clients
  • You need to perform token exchange directly from the client application

If you have a traditional web application with a secure backend, consider using the Authorization Code Flow instead, which uses a client_secret for additional security.

Generating Code Verifier and Challenge

To implement PKCE, you need to generate a code_verifier and its corresponding code_challenge. Here's how to do it:

Step 1: Generate Code Verifier

Create a cryptographically random string between 43 and 128 characters using the characters A-Z, a-z, 0-9, and the punctuation characters -._~.

JavaScript Example:

function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}

const code_verifier = generateCodeVerifier();

Node.js Example:

const crypto = require('crypto');
const base64url = require('base64url');

// Generate random bytes and encode
const randomBytes = crypto.randomBytes(32);
const code_verifier = base64url.encode(randomBytes);

Important: Store the code_verifier securely in your application (e.g., in memory or secure storage). You'll need it for the token exchange request.

Step 2: Generate Code Challenge

Create a SHA256 hash of the code_verifier and base64url encode it to create the code_challenge.

JavaScript Example:

async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64UrlEncode(hash);
}

const code_challenge = await generateCodeChallenge(code_verifier);

Node.js Example:

const crypto = require('crypto');
const base64url = require('base64url');

const hash = crypto.createHash('sha256').update(code_verifier).digest();
const code_challenge = base64url.encode(hash);

The code_challenge is what you send in the authorization request. The code_verifier is kept secret and only sent during token exchange.

Technical Integration

The following APIs are used in the PKCE flow:

APIDescriptionLink to API
Step 3: Start AuthorizationTo perform an authorization request with code_challengeView API
Step 4: Initiate AuthenticationTo start the user authentication (password or passwordless)View API
Step 4: Perform AuthenticationTo finish the user authenticationView API
Step 4: Continue Login ProcessTo continue the authentication and receive the authorization codeView API
Step 6: Exchange CodeTo exchange the code for a token using code_verifierView API

Complete Implementation Example

Here's a complete example showing how to implement the PKCE Flow:

Step 1-2: Generate Code Verifier and Challenge

// Generate code_verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}

// Generate code_challenge
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64UrlEncode(hash);
}

const code_verifier = generateCodeVerifier();
const code_challenge = await generateCodeChallenge(code_verifier);

Step 3: Authorization Request

Construct the authorization URL with the code_challenge:

GET https://{{base_url}}/authz-srv/authz?client_id={{client_id}}&redirect_uri={{redirect_uri}}&response_type=code&scope={{scope}}&state={{random_state_string}}&code_challenge={{code_challenge}}&code_challenge_method=S256

Example URL:

https://your-domain.cidaas.de/authz-srv/authz?client_id=your-client-id&redirect_uri=https://your-app.com/callback&response_type=code&scope=openid profile&state=random-state-123&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

Step 5: Extract Authorization Code

After successful authentication, extract the code from the redirect URI:

https://your-app.com/callback?code=authorization-code-here&state=random-state-123

Step 6: Token Exchange (Client-Side)

Exchange the authorization code for tokens using the code_verifier:

POST https://{{base_url}}/token-srv/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id={{client_id}}
&code={{authorization_code}}
&redirect_uri={{redirect_uri}}
&code_verifier={{code_verifier}}

cURL Example:

curl --location --request POST 'https://your-domain.cidaas.de/token-srv/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=your-client-id' \
--data-urlencode 'code=authorization-code-from-callback' \
--data-urlencode 'redirect_uri=https://your-app.com/callback' \
--data-urlencode 'code_verifier=your-stored-code-verifier'

Note: Unlike the Authorization Code Flow, PKCE allows the token exchange to be performed directly from the client application (SPA, mobile app) without requiring a backend server, since no client_secret is needed.

Need Support?

Please contact us directly on our support page