Skip to content

PKCE and Verification Redirects

Learn how PKCE secures email verification, password reset, email change, and OAuth flows by preventing authorization code interception.

PKCE code challenge code verifier token exchange verification redirect authorization code security

PKCE (Proof Key for Code Exchange, RFC 7636) prevents authorization code interception attacks. Instead of including a refresh token directly in a redirect URL, the server returns an authorization code that can only be exchanged by presenting the original code verifier.

PKCE is used across multiple authentication flows:

  1. The client generates a random codeVerifier and derives a codeChallenge using SHA-256
  2. The codeChallenge is sent with the initial authentication request
  3. When the user completes verification (clicks an email link, completes OAuth), the server returns an authorization code
  4. The client exchanges the code + original codeVerifier via POST /token/exchange
  5. The server validates that SHA256(codeVerifier) == storedCodeChallenge and returns a session

Authorization codes are single-use and expire after 5 minutes.

The JavaScript SDK provides a helper for generating PKCE pairs:

import { generatePKCEPair } from '@nhost/nhost-js/auth';
const { verifier, challenge } = await generatePKCEPair();

Email verification links and OAuth callbacks redirect to your app with a code query parameter. Your verification page must exchange this code for a session using the stored PKCE verifier. Below you can find an example page for React:

import { useEffect, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { nhost } from './lib/nhost';
export default function Verify() {
const navigate = useNavigate();
const location = useLocation();
const [status, setStatus] = useState('verifying');
const [error, setError] = useState('');
useEffect(() => {
const params = new URLSearchParams(location.search);
const code = params.get('code');
if (!code) {
setStatus('error');
setError('No authorization code found in URL');
return;
}
async function exchangeCode() {
// Retrieve and consume the stored verifier
const codeVerifier = localStorage.getItem('nhost_pkce_verifier');
localStorage.removeItem('nhost_pkce_verifier');
if (!codeVerifier) {
setStatus('error');
setError(
'No PKCE verifier found. The flow must be initiated from the same browser.',
);
return;
}
try {
await nhost.auth.tokenExchange({ code, codeVerifier });
setStatus('success');
navigate('/profile');
} catch (err) {
setStatus('error');
setError(`Verification failed: ${err.message}`);
}
}
exchangeCode();
}, [location.search, navigate]);
// Render based on status...
}

Check our tutorials for more detailed examples.