OpenID Connect and OAuth 2.0
This document explains the standard protocols for identity management and authorization in web applications.
What is OAuth 2.0
OAuth 2.0 is an open standard protocol for authorization.
Main Purpose
- Allow users to grant third-party applications access to their resources (data)
- Delegate access permissions to specific resources without sharing passwords
Four Key Roles in OAuth 2.0
- Resource Owner: The owner of the resource (typically the end user)
- Client: The application that wants to access the resource
- Authorization Server: The server that issues access tokens
- Resource Server: The server that provides protected resources
Main OAuth 2.0 Flows
Authorization Code Flow
The most secure and commonly recommended flow.
Characteristics:
- Does not pass sensitive information (access token) directly to the frontend
- High security due to token exchange on the backend
- Can be used with SPAs and native apps in combination with PKCE extension
Authorization Code Flow with PKCE
PKCE (Proof Key for Code Exchange) is an OAuth 2.0 extension that enhances security by preventing authorization code interception attacks.
Why PKCE is Needed:
- SPAs and mobile apps cannot safely store client_secret
- Authorization codes may be intercepted
- Enhances security for public clients (clients without client secrets)
How PKCE Works:
PKCE Parameters:
-
code_verifier:
- Random string of 43-128 characters
- Generated and retained locally by the client
- Example:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
-
code_challenge:
- Base64URL encoded SHA256 hash of code_verifier
- Sent during authorization request
- Example:
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
-
code_challenge_method:
S256(SHA256 recommended) orplain(not recommended)
Implementation Example:
// 1. Generate code_verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// 2. Generate code_challenge
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
// Base64URL encoding
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// 3. Authorization request
async function startAuthFlow() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store code_verifier in session storage
sessionStorage.setItem('code_verifier', codeVerifier);
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateRandomState());
window.location.href = authUrl.toString();
}
// 4. Callback handling (token acquisition)
async function handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
// Verify state
if (state !== sessionStorage.getItem('state')) {
throw new Error('Invalid state');
}
// Retrieve saved code_verifier
const codeVerifier = sessionStorage.getItem('code_verifier');
// Request to token endpoint
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
code_verifier: codeVerifier, // PKCE key parameter
}),
});
const tokens = await tokenResponse.json();
// Clean up code_verifier
sessionStorage.removeItem('code_verifier');
sessionStorage.removeItem('state');
return tokens;
}
Benefits of PKCE:
- ✅ No client_secret needed (ideal for SPAs and mobile apps)
- ✅ Prevents authorization code interception attacks
- ✅ Even if an attacker intercepts the code, they cannot obtain tokens without the code_verifier
- ✅ Recommended standard for all OAuth 2.0 clients
Security Points:
- code_verifier must be sufficiently random and unpredictable
- code_verifier is only retained on the client side and not sent (except during token exchange)
- code_challenge is securely stored on the authorization server side
- Use
S256for code_challenge_method (plainis not recommended)
Implicit Flow
※ Currently deprecated. For SPAs, use Authorization Code Flow with PKCE.
Client Credentials Flow
A flow used for server-to-server communication.
Client → Authorization Server: client_id + client_secret
Authorization Server → Client: Access token
Client → Resource Server: API access with access token
Use Cases:
- Microservice communication
- Batch processing
- API calls that do not require user context
What is OpenID Connect (OIDC)
OpenID Connect is an authentication layer built on top of OAuth 2.0.
Differences from OAuth 2.0
| Item | OAuth 2.0 | OpenID Connect |
|---|---|---|
| Main Purpose | Authorization | Authentication |
| Information Obtained | Access permissions | User ID information |
| Tokens | Access Token | ID Token + Access Token |
| Use Cases | API access delegation | Login/SSO |
ID Token
The core concept of OpenID Connect is the ID Token.
- JWT (JSON Web Token) format
- Contains user authentication information
- Signed and tamper-verifiable
ID Token Structure Example:
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "client-app-id",
"exp": 1735689600,
"iat": 1735686000,
"name": "Taro Yamada",
"email": "yamada@example.com",
"email_verified": true
}
Key Claims:
iss: Token issuersub: Unique user identifieraud: Target client for the tokenexp: Expiration timeiat: Issued at time
OpenID Connect Flow
Basically the same as OAuth 2.0's authorization code flow, but with the following differences:
-
Include
openidin scopescope=openid profile email -
ID Token is issued
- ID Token is obtained in addition to Access Token
- ID Token contains user information
-
UserInfo Endpoint
- Can obtain more detailed user information
GET /userinfoAuthorization: Bearer {access_token}
Implementation Examples
Client-side Authorization Request
// Redirect to authorization endpoint
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', generateRandomState()); // CSRF protection
window.location.href = authUrl.toString();
Backend Token Acquisition
// Receive authorization code in callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state (CSRF protection)
if (state !== req.session.state) {
return res.status(400).send('Invalid state');
}
// Request to token endpoint
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
client_secret: 'your-client-secret',
}),
});
const tokens = await tokenResponse.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token
// Verify ID Token
const idToken = verifyJWT(tokens.id_token);
// Save to session
req.session.userId = idToken.sub;
req.session.accessToken = tokens.access_token;
res.redirect('/dashboard');
});
Security Considerations
Required Measures
-
Use HTTPS
- All communication should be over HTTPS
-
State Parameter
- Use state parameter for validation to prevent CSRF attacks
-
PKCE (Proof Key for Code Exchange)
- Required for SPAs and mobile apps
- Prevents authorization code interception attacks
-
Secure Token Storage
- Properly protect Access Tokens
- Do not store in browser LocalStorage (XSS protection)
-
Scope Minimization
- Request only the minimum necessary scopes
-
Token Expiration
- Set short expiration times for Access Tokens
- Implement refresh mechanism with Refresh Tokens
Major Providers
- Azure Active Directory (Microsoft Entra ID)
- Auth0
- Okta
- Google Identity Platform
- Amazon Cognito
- Keycloak (Open Source)
Summary
- OAuth 2.0: Authorization framework for API access
- OpenID Connect: Authentication protocol based on OAuth 2.0
- Authorization Code Flow: Most secure and recommended flow
- ID Token: JWT containing user authentication information
- Security: Measures such as HTTPS, State, and PKCE are essential