OIDC Authentication Implementation: Step-by-Step Guide

Implement OpenID Connect authentication in your applications. Learn about OIDC flows, token management, and integration with popular identity providers.

What is OpenID Connect (OIDC)?

OpenID Connect is an identity layer built on top of OAuth 2.0. It provides a simple way to verify the identity of users based on authentication performed by an authorization server, as well as to obtain basic profile information about the user.

OIDC vs OAuth 2.0

While OAuth 2.0 is primarily about authorization (what you can do), OIDC adds authentication (who you are) on top of it:

  • OAuth 2.0: "Can this app access my data?"
  • OIDC: "Who is this user and can this app access their data?"

OIDC Authentication Flows

1. Authorization Code Flow (Recommended)

The most secure flow for web applications:

// Step 1: Redirect to authorization server
const authUrl = `${oidcConfig.authorization_endpoint}?
  client_id=${clientId}&
  response_type=code&
  scope=openid profile email&
  redirect_uri=${redirectUri}&
  state=${state}&
  nonce=${nonce}`;

window.location.href = authUrl;

2. Implicit Flow (Deprecated)

Less secure, mainly for single-page applications (not recommended for new implementations):

// Implicit flow example (avoid in new projects)
const authUrl = `${oidcConfig.authorization_endpoint}?
  client_id=${clientId}&
  response_type=id_token token&
  scope=openid profile email&
  redirect_uri=${redirectUri}&
  state=${state}&
  nonce=${nonce}`;

Complete OIDC Implementation

1. Discovery Configuration

First, discover the OIDC provider's configuration:

// Discover OIDC configuration
async function discoverOIDCConfig(issuer) {
  const discoveryUrl = `${issuer}/.well-known/openid_configuration`;
  const response = await fetch(discoveryUrl);
  return await response.json();
}

// Example usage
const oidcConfig = await discoverOIDCConfig('https://accounts.google.com');
console.log(oidcConfig);

2. Client Registration

Register your application with the OIDC provider:

// Client configuration
const clientConfig = {
  client_id: 'your-client-id',
  client_secret: 'your-client-secret', // For confidential clients
  redirect_uris: ['https://yourapp.com/callback'],
  response_types: ['code'],
  grant_types: ['authorization_code', 'refresh_token'],
  scope: 'openid profile email',
  token_endpoint_auth_method: 'client_secret_post'
};

3. Authorization Request

Initiate the authentication flow:

function initiateAuth() {
  const state = generateRandomString(32);
  const nonce = generateRandomString(32);
  
  // Store state and nonce for validation
  sessionStorage.setItem('oidc_state', state);
  sessionStorage.setItem('oidc_nonce', nonce);
  
  const params = new URLSearchParams({
    client_id: clientConfig.client_id,
    response_type: 'code',
    scope: 'openid profile email',
    redirect_uri: clientConfig.redirect_uris[0],
    state: state,
    nonce: nonce,
    response_mode: 'query'
  });
  
  const authUrl = `${oidcConfig.authorization_endpoint}?${params}`;
  window.location.href = authUrl;
}

4. Token Exchange

Exchange authorization code for tokens:

async function exchangeCodeForTokens(code, state) {
  // Validate state parameter
  const storedState = sessionStorage.getItem('oidc_state');
  if (state !== storedState) {
    throw new Error('Invalid state parameter');
  }
  
  const tokenResponse = await fetch(oidcConfig.token_endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      redirect_uri: clientConfig.redirect_uris[0],
      client_id: clientConfig.client_id,
      client_secret: clientConfig.client_secret
    })
  });
  
  const tokens = await tokenResponse.json();
  return tokens;
}

5. ID Token Validation

Validate the received ID token:

async function validateIdToken(idToken, nonce) {
  // Decode JWT header and payload
  const [header, payload, signature] = idToken.split('.');
  const decodedPayload = JSON.parse(atob(payload));
  
  // Validate claims
  const now = Math.floor(Date.now() / 1000);
  
  // Check expiration
  if (decodedPayload.exp < now) {
    throw new Error('ID token expired');
  }
  
  // Check issued at
  if (decodedPayload.iat > now) {
    throw new Error('ID token issued in the future');
  }
  
  // Check nonce
  if (decodedPayload.nonce !== nonce) {
    throw new Error('Invalid nonce');
  }
  
  // Check audience
  if (decodedPayload.aud !== clientConfig.client_id) {
    throw new Error('Invalid audience');
  }
  
  // Verify signature (simplified - use proper JWT library)
  // In production, use a JWT library like jsonwebtoken or jose
  
  return decodedPayload;
}

Popular OIDC Providers

Google OIDC

const googleOIDC = {
  issuer: 'https://accounts.google.com',
  client_id: 'your-google-client-id',
  client_secret: 'your-google-client-secret',
  redirect_uri: 'https://yourapp.com/callback',
  scope: 'openid profile email'
};

Auth0 OIDC

const auth0OIDC = {
  issuer: 'https://your-domain.auth0.com',
  client_id: 'your-auth0-client-id',
  client_secret: 'your-auth0-client-secret',
  redirect_uri: 'https://yourapp.com/callback',
  scope: 'openid profile email'
};

Microsoft Azure AD

const azureOIDC = {
  issuer: 'https://login.microsoftonline.com/your-tenant-id/v2.0',
  client_id: 'your-azure-client-id',
  client_secret: 'your-azure-client-secret',
  redirect_uri: 'https://yourapp.com/callback',
  scope: 'openid profile email'
};

Token Management

Access Token Refresh

async function refreshAccessToken(refreshToken) {
  const response = await fetch(oidcConfig.token_endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: clientConfig.client_id,
      client_secret: clientConfig.client_secret
    })
  });
  
  const tokens = await response.json();
  return tokens;
}

Token Storage

// Secure token storage
function storeTokens(tokens) {
  // Store access token in memory (short-lived)
  sessionStorage.setItem('access_token', tokens.access_token);
  
  // Store refresh token securely (long-lived)
  // Consider using httpOnly cookies or secure storage
  localStorage.setItem('refresh_token', tokens.refresh_token);
  
  // Store ID token for user info
  sessionStorage.setItem('id_token', tokens.id_token);
}

function clearTokens() {
  sessionStorage.removeItem('access_token');
  sessionStorage.removeItem('id_token');
  localStorage.removeItem('refresh_token');
}

Error Handling

function handleOIDCError(error) {
  switch (error.error) {
    case 'access_denied':
      console.log('User denied access');
      break;
    case 'invalid_request':
      console.log('Invalid request parameters');
      break;
    case 'invalid_client':
      console.log('Invalid client configuration');
      break;
    case 'invalid_grant':
      console.log('Invalid authorization code');
      break;
    case 'unsupported_response_type':
      console.log('Unsupported response type');
      break;
    case 'invalid_scope':
      console.log('Invalid scope requested');
      break;
    default:
      console.log('Unknown OIDC error:', error);
  }
}

Security Best Practices

  • Use HTTPS: Always use HTTPS in production
  • Validate State: Always validate the state parameter
  • Use Nonce: Include nonce for replay attack prevention
  • Token Validation: Always validate ID tokens on the server
  • Secure Storage: Store tokens securely (httpOnly cookies recommended)
  • Token Expiration: Implement proper token refresh logic

Test OIDC Flows

Use our OIDC playground to test and debug your authentication flows with various identity providers.

Use OIDC Playground