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​