← Back to all snippets
JAVASCRIPT

Implementing OAuth 2.0 PKCE Flow for SPAs

Securely integrate OAuth 2.0 with PKCE (Proof Key for Code Exchange) in your Single-Page Application to authorize users without exposing client secrets.

// Helper to generate a random string for code_verifier
function generateRandomString(length) {
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let randomString = '';
  for (let i = 0; i < length; i++) {
    randomString += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return randomString;
}

// Helper to base64url encode an array buffer
function base64urlencode(buffer) {
  const charCodes = new Uint8Array(buffer);
  const binary = String.fromCharCode(...charCodes);
  return btoa(binary)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

// Helper to generate a code_challenge from a code_verifier
async function generateCodeChallenge(codeVerifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(codeVerifier);
  const digest = await window.crypto.subtle.digest('SHA-256', data);
  return base64urlencode(digest);
}

// --- Main PKCE Flow ---

async function initiatePKCEAuth(clientId, redirectUri, authEndpoint, tokenEndpoint, scope) {
  const codeVerifier = generateRandomString(128);
  const codeChallenge = await generateCodeChallenge(codeVerifier);

  // Store codeVerifier for later use (e.g., in localStorage)
  localStorage.setItem('pkce_code_verifier', codeVerifier);

  const params = new URLSearchParams({
    response_type: 'code',
    client_id: clientId,
    redirect_uri: redirectUri,
    scope: scope,
    code_challenge_method: 'S256',
    code_challenge: codeChallenge
  });

  window.location.href = `${authEndpoint}?${params.toString()}`;
}

async function exchangePKCECodeForTokens(clientId, redirectUri, tokenEndpoint, code) {
  const codeVerifier = localStorage.getItem('pkce_code_verifier');
  if (!codeVerifier) {
    throw new Error('Code Verifier not found. Authentication flow interrupted.');
  }
  localStorage.removeItem('pkce_code_verifier'); // Clear after use

  const params = new URLSearchParams({
    grant_type: 'authorization_code',
    client_id: clientId,
    redirect_uri: redirectUri,
    code: code,
    code_verifier: codeVerifier
  });

  const response = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: params.toString()
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.statusText}`);
  }

  const tokenData = await response.json();
  // Store and use tokenData (e.g., access_token, id_token)
  console.log('Successfully obtained tokens:', tokenData);
  return tokenData;
}

// Example Usage (assuming you are on the redirect_uri after authorization):
// const CLIENT_ID = 'your-client-id';
// const REDIRECT_URI = window.location.origin + '/callback';
// const AUTH_ENDPOINT = 'https://accounts.example.com/oauth/authorize';
// const TOKEN_ENDPOINT = 'https://accounts.example.com/oauth/token';
// const SCOPE = 'openid profile email';

// // On initial button click to log in:
// // initiatePKCEAuth(CLIENT_ID, REDIRECT_URI, AUTH_ENDPOINT, TOKEN_ENDPOINT, SCOPE);

// // On redirect_uri page load:
// // const urlParams = new URLSearchParams(window.location.search);
// // const authCode = urlParams.get('code');
// // if (authCode) {
// //   exchangePKCECodeForTokens(CLIENT_ID, REDIRECT_URI, TOKEN_ENDPOINT, authCode)
// //     .catch(err => console.error('PKCE Token Exchange Error:', err));
// // }
How it works: This JavaScript snippet demonstrates the client-side implementation of the OAuth 2.0 PKCE (Proof Key for Code Exchange) flow. It involves generating a `code_verifier` and its `code_challenge`, redirecting the user to the authorization server, and then exchanging the authorization code for tokens using the stored `code_verifier`. PKCE is crucial for Single-Page Applications (SPAs) as it enhances security by mitigating authorization code interception attacks without requiring a client secret, making it suitable for public clients.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs