Securing Public Clients with OAuth 2.0 Authorization Code Grant + PKCE
Learn how PKCE turns the OAuth 2.0 Authorization Code Grant into a safe flow for mobile and single-page apps, with a step-by-step example, trade-offs, and a quick checklist to get you started.
12 Jul 2026, 20:26 UTC

Why PKCE Matters for Public Clients
Public clients—mobile apps, single-page applications, or any client that cannot keep a secret—must rely on OAuth 2.0 without a client secret. The classic Authorization Code Grant protects the code by binding it to a client secret, which public clients lack. PKCE (Proof Key for Code Exchange) fills this gap by adding a dynamic code challenge that ties the authorization request to the token request. This eliminates the risk that an intercepted authorization code can be swapped for tokens by an attacker.
Step-by-Step PKCE Flow
- Generate a Code Verifier
- Random string, 43–128 characters, using unreserved characters (A-Z, a-z, 0-9, "-", ".", "_", "~").
- In many libraries this is a base64url-encoded random byte array.
- Create a Code Challenge
- Hash the verifier with SHA-256.
- Encode the hash in base64url. This is the
code_challenge. - Set
code_challenge_method=S256in the request.
- Authorization Request
- Send
code_challengeandcode_challenge_methodto the provider's authorization endpoint. - Redirect the user to the authorization URL; the user authenticates and authorizes.
https://auth.example.com/authorize?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=YOUR_REDIRECT_URI &scope=openid profile &code_challenge=REPLACE_WITH_CHALLENGE &code_challenge_method=S256
- Send
- Receive Authorization Code
- The provider redirects to
redirect_uriwithcode=AUTH_CODE.
- The provider redirects to
- Token Exchange
- POST to the token endpoint with the original
code_verifier. - If the verifier matches the challenge, the provider returns an access token (and optionally a refresh token).
curl -X POST https://auth.example.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE" \ -d "redirect_uri=YOUR_REDIRECT_URI" \ -d "client_id=YOUR_CLIENT_ID" \ -d "code_verifier=REPLACE_WITH_VERIFIER"
- POST to the token endpoint with the original
Worked Example with cURL
Below is a minimal, fully commented example that you can copy into a terminal. Replace placeholders with your values. This example assumes the provider supports PKCE and that TLS is correctly configured.
# Generate a verifier
CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '+/=' | cut -c1-64)
# Create the challenge
CODE_CHALLENGE=$(printf "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
# Build the authorization URL
AUTH_URL="https://auth.example.com/authorize?response_type=code"
AUTH_URL="$AUTH_URL&client_id=YOUR_CLIENT_ID"
AUTH_URL="$AUTH_URL&redirect_uri=https://your.app/callback"
AUTH_URL="$AUTH_URL&scope=openid profile"
AUTH_URL="$AUTH_URL&code_challenge=${CODE_CHALLENGE}"
AUTH_URL="$AUTH_URL&code_challenge_method=S256"
echo "Open the following URL in a browser:"
echo "$AUTH_URL"
# After user logs in, you will receive a redirect like:
# https://your.app/callback?code=AUTH_CODE
# Capture the code (replace with actual value)
AUTH_CODE=REPLACE_WITH_CODE_FROM_QUERY
# Exchange for tokens
curl -X POST https://auth.example.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=${AUTH_CODE}" \
-d "redirect_uri=https://your.app/callback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code_verifier=${CODE_VERIFIER}"
Trade-offs and Limitations
- No Client Secret Needed
- Public clients avoid storing secrets, reducing credential exposure.
- However, if the provider does not support PKCE, the Authorization Code Grant reverts to a risky state.
- Redirect URI and TLS Still Critical
- PKCE protects the code, but the redirect URI must be trusted and served over HTTPS to avoid token leakage.
- Refresh Tokens
- Some providers issue refresh tokens only to confidential clients. Check the provider's documentation before assuming long-lived sessions.
- Implementation Complexity
- Generating and storing the verifier securely adds a small overhead. Most modern OAuth libraries handle this automatically.
Actionable Checklist
- Verify that your OAuth provider advertises PKCE support in its documentation.
- Use a trusted OAuth library for your platform; most include PKCE support out of the box.
- Generate a high-entropy code verifier (≥ 43 characters) and store it only for the duration of the flow.
- Always serve the redirect URI over TLS and restrict it to a known set of URLs.
- Inspect the network traffic during development to confirm the code challenge is sent and the verifier is included in the token request.
- Test the flow against a sandbox or test account before going live.
- Document the flow in your security policy and ensure your team understands the PKCE steps.
Implementing PKCE turns the Authorization Code Grant into a robust, secret-less flow suitable for mobile and SPA clients. By following the steps above, you can protect your users' tokens without exposing any credentials.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.