PKCE Code Flow

Why PKCE?

PKCE (Proof Key for Code Exchange) prevents attackers from stealing authorization codes. You generate a random code_verifier, hash it into a code_challenge, and send code_challenge in the authorization endpoint. Later, you must provide the plain code_verifier at the token endpoint. If they don’t match, no tokens are issued.

In line with strong cryptographic standards, MicroAuth only supports the S256 PKCE challenge method. We do not allow plain, as S256 significantly enhances security by hashing the code verifier using SHA-256.

We highly recommend PKCE implementation for both backend and client-side apps. The following PKCE code generation example is for the JS implementation. You should use the programming language of your application to generate these codes in production.

Step 1: Obtain OAuth Endpoints

The endpoints required for OAuth flow can be obtained using the OpenID configuration endpoint of a tenant available at https://<tenant-domain>/.well-known/openid-configuration

Step 2: Generate PKCE Codes

Generate a code verifier and also a code challenge (hashed) derived from the code verifier. Store both values securely, preferably in a session storage. You will need to send the code challenge hash in code_challenge query parameter when sending the user to authorization endpoint and you will need to provide the code verifier in the code_verifier form field when exchanging the code for tokens at the token endpoint.

const crypto = require("crypto");

async function createPKCECodes() {
  // Random verifier (43+ chars)
  const randomBytes = new Uint8Array(32);
  crypto.getRandomValues(randomBytes);
  const codeVerifier = btoa(String.fromCharCode(...randomBytes))
    .replace(/[^a-zA-Z0-9]/g, "")
    .slice(0, 43);

  // Hash + base64url for code_challenge
  const encoder = new TextEncoder();
  const data = encoder.encode(codeVerifier);
  const hash = await crypto.subtle.digest("SHA-256", data);
  return {
    codeVerifier,
    codeChallenge: btoa(String.fromCharCode(...new Uint8Array(hash)))
      .replace(/\+/g, "-")
      .replace(/\//g, "_")
      .replace(/=+$/, "")
  };
}

Step 3: Redirect User

After generating the PKCE codes, build a redirect URL to authorization endpoint using the following parameters in the query and then redirect the user to that redirect URL:

  • response_type=code

  • client_id

  • redirect_uri

  • scope

  • code_challenge=<hash>

  • code_challenge_method=S256

  • state=<optional opaque token for CSRF protection>

Step 4: Obtain Authorization Code

Once successfully authorized, the user will be redirected back to the provided redirect_uri with a code in the query parameter.

Step 5: Exchange Code for Access Tokens

Make a POST (Content-Type: application/x-www-form-urlencoded) request to token endpoint with the following fields in the form data to obtain the JWT access and refresh tokens:

  • grant_type (authorization_code)

  • code (Code from the query parameter)

  • code_verifier (That you generated earlier)

  • redirect_uri (Same as used with the authorization endpoint)

That’s It

You will successfully, and securely obtain JWT tokens following the above steps. For further security, consider the following tips:

  • SPAs should store tokens in memory or short-term, avoiding localStorage if possible (XSS risk).

  • Backend apps do the same PKCE flow but handle hashing on the server.

  • PKCE is now the recommended standard for secure OAuth and OpenID Connect, replacing older “implicit” flows for public clients.