DEV Community

Said Olano
Said Olano

Posted on

OAuth2 and OpenID Connect: A Practical Implementation Guide (2026-08-20 14:33)

OAuth2 and OpenID Connect: A Practical Implementation Guide

Modern applications rarely handle authentication and authorization in isolation. Instead, they rely on battle-tested standards like OAuth2 and OpenID Connect (OIDC). This post breaks down what these protocols do, how they differ, and how to implement them correctly.

OAuth2 vs. OpenID Connect

A common source of confusion is treating these as interchangeable. They are not:

  • OAuth2 is an authorization framework. It lets an application obtain limited access to a user's resources without exposing credentials.
  • OpenID Connect is an authentication layer built on top of OAuth2. It adds identity by introducing the ID Token.

In short: OAuth2 answers "Can this app access these resources?" while OIDC answers "Who is this user?"

Core Roles

Role Description
Resource Owner The user who owns the data
Client The application requesting access
Authorization Server Issues tokens (e.g., Auth0, Keycloak, Okta)
Resource Server The API hosting protected resources

The Authorization Code Flow with PKCE

For web and mobile apps, the Authorization Code Flow with PKCE (Proof Key for Code Exchange) is the recommended approach. It mitigates authorization code interception attacks.

Step 1: Generate a Code Verifier and Challenge

import crypto from 'crypto';

function base64URLEncode(buffer) {
  return buffer.toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

const codeVerifier = base64URLEncode(crypto.randomBytes(32));
const codeChallenge = base64URLEncode(
  crypto.createHash('sha256').update(codeVerifier).digest()
);
Enter fullscreen mode Exit fullscreen mode

Step 2: Redirect the User to the Authorization Endpoint

GET https://auth.example.com/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://app.example.com/callback
  &scope=openid profile email
  &state=RANDOM_STATE
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256
Enter fullscreen mode Exit fullscreen mode

Note: The openid scope is what triggers OIDC behavior and requests an ID Token.

Step 3: Exchange the Code for Tokens

After the user authenticates, the authorization server redirects back with a code. Exchange it at the token endpoint:

const response = await fetch('https://auth.example.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: 'https://app.example.com/callback',
    client_id: 'YOUR_CLIENT_ID',
    code_verifier: codeVerifier
  })
});

const tokens = await response.json();
// { access_token, id_token, refresh_token, expires_in, token_type }
Enter fullscreen mode Exit fullscreen mode

Validating the ID Token

The ID Token is a JWT. Never trust it without validation. At minimum, verify:

  1. Signature using the provider's public keys (JWKS endpoint).
  2. iss matches the expected issuer.
  3. aud matches your client ID.
  4. exp is in the future.
  5. nonce matches the value you sent (if used).
import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
);

const { payload } = await jwtVerify(idToken, JWKS, {
  issuer: 'https://auth.example.com/',
  audience: 'YOUR_CLIENT_ID'
});

console.log(`Authenticated user: ${payload.sub}`);
Enter fullscreen mode Exit fullscreen mode

Protecting the Resource Server

The API validates the access token on each request. For opaque tokens, use token introspection; for JWTs, verify locally.

function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }
  const token = authHeader.slice(7);
  jwtVerify(token, JWKS, { issuer, audience })
    .then(({ payload }) => { req.user = payload; next(); })
    .catch(() => res.status(401).json({ error: 'Invalid token' }));
}
Enter fullscreen mode Exit fullscreen mode

Security Best Practices

  • Always use PKCE, even for confidential clients.
  • Validate state to prevent CSRF.
  • Use short-lived access tokens and rotate refresh tokens.
  • Store tokens securely — prefer HTTP-only cookies over localStorage for browser apps.
  • Request minimal scopes following the principle of least privilege.
  • Never use the Implicit Flow — it is deprecated in favor of Authorization Code + PKCE.

Conclusion

OAuth2 and OpenID Connect provide a robust foundation for secure authentication and authorization. The key is understanding the separation of concerns: OAuth2 for access, OIDC for identity. By adopting the Authorization Code Flow with PKCE and rigorously validating tokens, you can build systems that are both user-friendly

Top comments (0)