Skip to main content

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

  1. Resource Owner: The owner of the resource (typically the end user)
  2. Client: The application that wants to access the resource
  3. Authorization Server: The server that issues access tokens
  4. 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:

  1. code_verifier:

    • Random string of 43-128 characters
    • Generated and retained locally by the client
    • Example: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  2. code_challenge:

    • Base64URL encoded SHA256 hash of code_verifier
    • Sent during authorization request
    • Example: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  3. code_challenge_method:

    • S256 (SHA256 recommended) or plain (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 S256 for code_challenge_method (plain is 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

ItemOAuth 2.0OpenID Connect
Main PurposeAuthorizationAuthentication
Information ObtainedAccess permissionsUser ID information
TokensAccess TokenID Token + Access Token
Use CasesAPI access delegationLogin/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 issuer
  • sub: Unique user identifier
  • aud: Target client for the token
  • exp: Expiration time
  • iat: Issued at time

OpenID Connect Flow

Basically the same as OAuth 2.0's authorization code flow, but with the following differences:

  1. Include openid in scope

    scope=openid profile email
  2. ID Token is issued

    • ID Token is obtained in addition to Access Token
    • ID Token contains user information
  3. UserInfo Endpoint

    • Can obtain more detailed user information
    GET /userinfo
    Authorization: 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

  1. Use HTTPS

    • All communication should be over HTTPS
  2. State Parameter

    • Use state parameter for validation to prevent CSRF attacks
  3. PKCE (Proof Key for Code Exchange)

    • Required for SPAs and mobile apps
    • Prevents authorization code interception attacks
  4. Secure Token Storage

    • Properly protect Access Tokens
    • Do not store in browser LocalStorage (XSS protection)
  5. Scope Minimization

    • Request only the minimum necessary scopes
  6. 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

References