OAuth 2.0 Authorization Code Flow
Vibe Prompt
"Help me implement OAuth 2.0 Authorization Code + PKCE Flow using Supabase as the Identity Provider."
Flow
1. Frontend → Supabase: Redirect to authorization page
GET https://<project>.supabase.co/auth/v1/authorize
?client_id=<client_id>
&redirect_uri=<redirect_uri>
&response_type=code
&code_challenge=<SHA256(code_verifier)>
&code_challenge_method=S256
&scope=openid profile email
2. User logs in and grants authorization
3. Supabase → Frontend: Redirect back to redirect_uri with authorization code
https://myapp.com/auth/callback?code=<auth_code>
4. Frontend → Backend: Send authorization code
5. Backend → Supabase: Exchange for Token
POST https://<project>.supabase.co/auth/v1/token
?grant_type=authorization_code
&code=<auth_code>
&code_verifier=<original_code_verifier>
&redirect_uri=<redirect_uri>
6. Supabase → Backend: access_token + refresh_token + id_token
7. Backend → Frontend: Set Session Cookie
Next.js API Route
// app/api/auth/callback/route.ts
import { createClient } from '@/utils/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
const next = searchParams.get('next') ?? '/'
if (code) {
const supabase = createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}${next}`)
}
}
return NextResponse.redirect(`${origin}/auth-error`)
}
PKCE Benefits
- ✅ Authorization codes are useless if intercepted during transmission (requires code_verifier)
- ✅ No client_secret needed (suitable for SPAs)
- ✅ Prevents authorization code interception attacks
Key Points
- ✅ Supplement specific learning points according to this chapter's topic
- ✅ Add comparison tables, code examples, or flowcharts
- ✅ Ensure content is solid and valuable
PKCE Flow Explanation
PKCE (Proof Key for Code Exchange) was designed to solve the problem of SPAs and mobile apps being unable to securely store a Client Secret. Traditional OAuth 2.0 Authorization Code Flow requires the client to authenticate using a client_secret, which is a sensitive credential that must be kept confidential. However, in Single Page Applications (SPAs) and mobile apps, the source code is accessible to users, making it impossible to securely store such secrets. PKCE introduces a mechanism to prove that the client initiating the token request is the same one that initiated the authorization request, without requiring a client_secret.
Complete Flow
1. Frontend generates Code Verifier (a random string of 43-128 characters)
2. Frontend calculates Code Challenge = SHA256(Code Verifier)
3. Frontend includes Code Challenge in the authorization request
4. Authorization server stores the Code Challenge and returns an authorization code
5. Frontend uses the authorization code + Code Verifier to exchange for tokens
6. Authorization server verifies SHA256(Verifier) == Challenge
7. If verification passes, the server issues the tokens
Code Verifier Generation
// Frontend JavaScript to generate Code Verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
async function generateCodeChallenge(verifier) {
const hash = await crypto.subtle.digest("SHA-256",
new TextEncoder().encode(verifier));
return base64URLEncode(new Uint8Array(hash));
}
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
S256 vs Plain
| Mode | Description | Security | |:----:|-------------|:--------:| | S256 | SHA-256 hashed Code Verifier | High (even if Code Challenge is intercepted, it cannot be reversed) | | plain | Code Verifier sent directly | Low (intercepting it is equivalent to having the code) |
Strongly recommend using S256. The plain mode is only for compatibility with legacy systems.
PKCE: Why Do SPAs and Apps Need Different Authorization Flows?
Traditional Authorization Code Flow requires a backend to securely store a Client Secret. However, SPAs (React, Vue) and mobile apps have source code that can be viewed by users—any secrets embedded in the code are effectively public. This creates a critical security vulnerability because an attacker could extract the client_secret and impersonate the application.
PKCE (pronounced "pixie") solves this problem by introducing a proof key mechanism that allows the client to prove its identity without needing a secret. Instead of relying on a static secret, PKCE uses a dynamic, one-time-use verifier that is generated during the authorization request and validated during the token exchange.
Traditional Flow vs PKCE Flow
| Traditional Flow | PKCE Flow | |:----------------:|:----------:| | Requires Client Secret | Does not require any Secret | | Can only be used in applications with a backend | Can be used in SPAs, Apps, IoT | | Authorization code interception is still a risk | Requires Code Verifier to exchange |
Real-World Example: React SPA + LINE Login
// React frontend directly generates Code Verifier and Challenge
const generatePKCE = async () => {
const verifier = Array.from({ length: 64 }, () =>
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
[Math.floor(Math.random() * 66)]
).join('');
sessionStorage.setItem('code_verifier', verifier);
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
// Redirect to LINE login page
window.location.href = `https://access.line.me/oauth2/v2.1/authorize?
response_type=code&
client_id=${LINE_CLIENT_ID}&
redirect_uri=${encodeURIComponent(window.location.origin + '/callback')}&
state=${crypto.randomUUID()}&
code_challenge=${challenge}&
code_challenge_method=S256`;
};
Understanding the Technical Components
Authorization Code
The authorization code is a temporary credential that represents the user's authorization decision. It is issued by the authorization server (e.g., Supabase) after the user successfully authenticates and grants permission. This code is short-lived and can only be used once. Its primary purpose is to obtain an access token, which allows the application to access the user's resources on their behalf.
Access Token
An access token is a credential that represents the authorization granted by the user. It is used to access protected resources on behalf of the user. Access tokens are typically sent in the Authorization header of HTTP requests to the resource server. They have a limited lifetime and can be refreshed using a refresh token.
Refresh Token
A refresh token is a long-lived token that can be used to obtain new access tokens without requiring the user to re-authenticate. This is particularly useful for maintaining user sessions over extended periods. Refresh tokens are usually stored securely on the backend and are never exposed to the client-side application.
ID Token
An ID token is a JSON Web Token (JWT) that contains information about the user's authentication. It is issued by the authorization server when using OpenID Connect, an extension of OAuth 2.0. The ID token includes claims such as the user's identifier, name, email, and other profile information.
Security Considerations
Why PKCE is Critical for SPAs
Single Page Applications run entirely in the browser, which means all code and configuration are visible to the user. If a traditional OAuth 2.0 flow were used, the client_secret would be exposed in the JavaScript bundle, making it trivial for attackers to extract and misuse. PKCE eliminates this vulnerability by replacing the static secret with a dynamic, cryptographically secure verifier that is generated at runtime and never stored in the code.
Preventing Authorization Code Interception
Even if an attacker intercepts the authorization code during the redirect phase (e.g., through a man-in-the-middle attack or by capturing the URL fragment), they cannot exchange it for tokens without the corresponding code_verifier. This is because the authorization server will only accept the code if the verifier matches the challenge that was originally sent.
Secure Storage of Code Verifier
The code_verifier must be stored securely on the client side until the token exchange occurs. In modern browsers, sessionStorage is a good choice because it is cleared when the page session ends, reducing the risk of long-term exposure. However, developers should ensure that the verifier is not logged or exposed in any client-side storage that persists beyond the session.
Implementation Best Practices
Generating Cryptographically Secure Random Values
When generating the code_verifier, it is essential to use a cryptographically secure random number generator. The Web Crypto API provides the crypto.getRandomValues() method, which is suitable for this purpose. Avoid using Math.random() for security-sensitive operations, as it is not cryptographically secure.
Proper Base64 URL Encoding
The code_challenge must be encoded using base64 URL encoding (base64url), which replaces '+' with '-', '/' with '_', and removes padding '=' characters. This ensures that the challenge can be safely included in URLs without requiring additional encoding.
Handling Token Exchange Errors
When exchanging the authorization code for tokens, always handle potential errors gracefully. Common errors include invalid_grant (code already used), invalid_client (incorrect client_id), and unauthorized_client (missing required parameters). Implement proper error logging and user feedback to help diagnose issues during development and production.
Session Management
After successfully obtaining tokens, the backend should establish a secure session for the user. This typically involves setting an HTTP-only, Secure, and SameSite cookie to store the session identifier. This prevents client-side JavaScript from accessing the session cookie, mitigating the risk of cross-site scripting (XSS) attacks.
Comparison with Other OAuth 2.0 Flows
Authorization Code Flow (Traditional)
The traditional Authorization Code Flow is designed for confidential clients, such as server-side web applications. It requires a client_secret to authenticate the token request, which must be kept confidential. This flow is not suitable for public clients like SPAs or mobile apps due to the inability to securely store the secret.
Implicit Flow
The Implicit Flow was historically used for public clients but has been largely deprecated due to security concerns. It returns the access token directly in the URL fragment, which can be intercepted. Modern best practices recommend using the Authorization Code Flow with PKCE instead.
Client Credentials Flow
The Client Credentials Flow is used for machine-to-machine authentication, where the client is acting on its own behalf rather than on behalf of a user. This flow does not involve user consent or authorization codes and is not applicable to the scenarios discussed here.
Business Value and Financial Return
Reduced Security Risks
Implementing PKCE significantly reduces the risk of authorization code interception attacks, which can lead to unauthorized access to user data and potential data breaches. By preventing these vulnerabilities, organizations can avoid costly security incidents, regulatory fines, and reputational damage.
Compliance with Modern Standards
PKCE is a requirement for OAuth 2.0 for Native Apps (RFC 8252) and is widely adopted across major identity providers. Implementing PKCE ensures that applications comply with industry standards and can integrate seamlessly with popular authentication services like Google, Facebook, and Apple.
Improved User Experience
By using PKCE, developers can build secure authentication flows for SPAs and mobile apps without compromising on user experience. Users can authenticate using familiar social login providers, and the application can maintain persistent sessions using refresh tokens, reducing the need for frequent logins.
Developer Productivity
Using established libraries and frameworks that support PKCE (such as Supabase Auth) reduces the development time and effort required to implement secure authentication. This allows developers to focus on building core application features rather than reinventing authentication mechanisms.
Next Chapter Preview: Integrating All Authorization Knowledge
From IAM fundamentals to RBAC/ABAC, from session management to OAuth PKCE—this chapter will integrate all knowledge into a comprehensive system from authentication to authorization.
Transition to the Next Chapter
Having mastered the intricacies of OAuth 2.0 Authorization Code Flow with PKCE, you now possess the foundational knowledge required to build secure, scalable authentication systems for modern web and mobile applications. This chapter has equipped you with the technical skills to implement industry-standard security practices, understand the underlying cryptographic principles, and make informed architectural decisions when designing authentication flows.
In the next chapter, we will build upon this foundation by exploring Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). You will learn how to design flexible permission systems that scale with your application's complexity, implement fine-grained access controls, and integrate these systems with the authentication mechanisms we've established. We'll also dive into session management strategies, including secure cookie handling, token refresh mechanisms, and logout procedures.
By the end of the course, you will have a complete understanding of how to architect and implement a robust authorization system that not only secures user data but also provides the flexibility needed for enterprise-grade applications. This knowledge will enable you to confidently tackle real-world commercial projects, ensuring that your implementations meet both security requirements and business objectives.
The skills you've developed in this chapter—particularly the ability to implement PKCE-enabled OAuth flows—are directly applicable to building production-ready applications that can be deployed with confidence. As you move forward, remember that authentication and authorization are not just technical challenges but critical components of your application's overall security posture and user trust.