← Back to Blog

Deep Dive: Advanced OIDC Scenarios

Complete developer blueprints and full code examples for all Zero IDS OAuth 2.0 / OpenID Connect flows

This document details integration scenarios across standard grant types, providing full, runnable Node.js and Express implementations for developers to build production integrations.

1. Authorization Code Flow with PKCE

Use Case: Most secure flow for Web Applications. Uses dynamic Proof Key for Code Exchange (PKCE) keys to protect tokens from interception.

Complete Node.js Implementation

const { Issuer, generators } = require('openid-client');
const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({
  secret: 'saas-session-secret',
  resave: false,
  saveUninitialized: false
}));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({
    client_id: 'saas-confidential-client-id',
    client_secret: 'saas-confidential-client-secret',
    redirect_uris: ['http://localhost:3001/callback'],
    response_types: ['code']
  });
})();

app.get('/login', (req, res) => {
  const code_verifier = generators.codeVerifier(); 
  const code_challenge = generators.codeChallenge(code_verifier); 
  const state = generators.state();

  req.session.code_verifier = code_verifier;
  req.session.state = state;

  const authUrl = client.authorizationUrl({
    scope: 'openid profile email offline_access', 
    code_challenge: code_challenge,
    code_challenge_method: 'S256',
    state: state
  });
  res.redirect(authUrl);
});

app.get('/callback', async (req, res) => {
  const params = client.callbackParams(req);
  try {
    const tokenSet = await client.callback('http://localhost:3001/callback', params, {
      code_verifier: req.session.code_verifier,
      state: req.session.state
    });
    
    req.session.tokenSet = tokenSet;
    req.session.userInfo = await client.userinfo(tokenSet.access_token);
    res.redirect('/profile');
  } catch (err) {
    res.status(400).send('Authorization validation failed: ' + err.message);
  }
});

app.get('/profile', (req, res) => {
  if (!req.session.tokenSet) return res.redirect('/login');
  res.json({ userInfo: req.session.userInfo, tokens: req.session.tokenSet });
});

app.listen(3001, () => console.log('App running on http://localhost:3001'));

2. Hybrid Flow

Use Case: Front-end apps receive ID tokens directly (for rendering client-side UI configurations immediately) while keeping access tokens restricted to back-channel exchange.

Complete Node.js Implementation

const { Issuer, generators } = require('openid-client');
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({ secret: 'hybrid-session-secret', resave: false, saveUninitialized: false }));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({
    client_id: 'hybrid-client-id',
    client_secret: 'hybrid-client-secret',
    redirect_uris: ['http://localhost:3001/callback'],
    response_types: ['code id_token'] // Ask for authorization code and ID token
  });
})();

app.get('/login', (req, res) => {
  const nonce = generators.nonce();
  const state = generators.state();
  req.session.nonce = nonce;
  req.session.state = state;

  const authUrl = client.authorizationUrl({
    scope: 'openid profile email',
    response_type: 'code id_token',
    response_mode: 'form_post', // Recommended mode to prevent token details leaking in URI logs
    nonce: nonce,
    state: state
  });
  res.redirect(authUrl);
});

// Since response_mode is form_post, handling callback as a POST request
app.post('/callback', async (req, res) => {
  try {
    const params = client.callbackParams(req);
    const tokenSet = await client.callback('http://localhost:3001/callback', params, {
      nonce: req.session.nonce,
      state: req.session.state
    });
    
    req.session.tokenSet = tokenSet;
    res.json({ message: 'Login successful via Hybrid Flow', claims: tokenSet.claims() });
  } catch (err) {
    res.status(500).send('Authentication Exchange failed: ' + err.message);
  }
});

app.listen(3001, () => console.log('App running on http://localhost:3001'));

3. Implicit Flow (Legacy)

Use Case: Legacy browser apps. Directly returns tokens in the URL fragment.

⚠️ Security Advisory: The Implicit Flow is deprecated due to access token leakage in HTTP referrer headers and browser history logs. Implement Authorization Code Flow with PKCE instead.

Complete HTML/JavaScript Implementation

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Legacy Implicit Flow Client</title>
</head>
<body>
    <h2>OIDC Implicit Flow Demo</h2>
    <button id="loginBtn">Log In</button>
    <div id="output"></div>

    <script>
        const clientId = 'legacy-implicit-spa-id';
        const issuerUrl = 'http://localhost:3000/oidc';
        const redirectUri = window.location.origin + window.location.pathname;

        document.getElementById('loginBtn').onclick = () => {
            const state = Math.random().toString(36).substring(2);
            const nonce = Math.random().toString(36).substring(2);
            localStorage.setItem('state', state);
            localStorage.setItem('nonce', nonce);

            const authUrl = `${issuerUrl}/auth` +
                `?response_type=id_token token` +
                `&client_id=${encodeURIComponent(clientId)}` +
                `&redirect_uri=${encodeURIComponent(redirectUri)}` +
                `&scope=openid profile email` +
                `&response_mode=fragment` +
                `&state=${state}` +
                `&nonce=${nonce}`;
            window.location.href = authUrl;
        };

        // Parse fragment tokens upon redirect callback
        if (window.location.hash) {
            const hash = window.location.hash.substring(1);
            const params = new URLSearchParams(hash);
            
            const accessToken = params.get('access_token');
            const idToken = params.get('id_token');
            const returnedState = params.get('state');

            // Validate state parameter to mitigate CSRF
            const savedState = localStorage.getItem('state');
            if (returnedState === savedState) {
                document.getElementById('output').innerText = `Login success!\nAccess Token: ${accessToken}\nID Token: ${idToken}`;
                window.location.hash = ''; // Clear tokens from address bar
            } else {
                document.getElementById('output').innerText = 'State mismatch. Possible CSRF attack detected!';
            }
        }
    </script>
</body>
</html>

4. Client Credentials Grant (M2M)

Use Case: Secure service-to-service Machine-to-Machine communication without user presence context.

Complete Node.js Implementation

const { Issuer } = require('openid-client');
const axios = require('axios');

async function performM2MCall() {
  try {
    // 1. Discover the Provider
    const issuer = await Issuer.discover('http://localhost:3000/oidc');
    
    // 2. Initialize Client with secret authentication methods
    const client = new issuer.Client({
      client_id: 'm2m-client-id',
      client_secret: 'm2m-client-secret-hex-key',
      token_endpoint_auth_method: 'client_secret_post' 
    });

    // 3. Execute client credentials grant request
    console.log('Requesting access token...');
    const tokenSet = await client.grant({
      grant_type: 'client_credentials',
      scope: 'api:read billing:write' // Request scopes configured for this client
    });

    console.log('M2M Access Token acquired successfully:', tokenSet.access_token);

    // 4. Request protected API endpoints
    const response = await axios.get('http://localhost:3002/protected-api', {
      headers: {
        Authorization: `Bearer ${tokenSet.access_token}`
      }
    });

    console.log('API Resource response data:', response.data);
  } catch (err) {
    console.error('M2M Integration Failed:', err.message);
  }
}

performM2MCall();

5. Refresh Token Flow

Use Case: Maintain uninterrupted sessions without forcing the user to log in again.

Complete Node.js Implementation

const { Issuer } = require('openid-client');
const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({ secret: 'refresh-session-secret', resave: false, saveUninitialized: false }));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({
    client_id: 'web-portal',
    client_secret: 'secret123'
  });
})();

// Middleware that automatically renews expired access tokens using the refresh token
async function checkAndRefreshTokens(req, res, next) {
  if (!req.session.tokenSet) {
    return res.status(401).send('Session not authenticated.');
  }

  // Restore TokenSet instance properties
  let tokenSet = new client.issuer.TokenSet(req.session.tokenSet);

  if (tokenSet.expired()) {
    console.log('Access Token expired. Requesting refresh grant...');
    try {
      const refreshed = await client.refresh(tokenSet.refresh_token);
      req.session.tokenSet = refreshed;
      console.log('Tokens refreshed successfully.');
    } catch (err) {
      console.error('Failed to refresh tokens. Redirecting to login:', err.message);
      return res.redirect('/login');
    }
  }
  next();
}

app.get('/api-data', checkAndRefreshTokens, (req, res) => {
  res.json({
    message: 'Authorized successfully',
    accessToken: req.session.tokenSet.access_token
  });
});

app.listen(3001);

6. Token Introspection (RFC 7662)

Use Case: Resource Servers (APIs) query the Identity Server directly to validate access tokens.

Complete Node.js API Middleware Implementation

const express = require('express');
const { Issuer } = require('openid-client');

const app = express();
let client;

(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({
    client_id: 'api-resource-server-id',
    client_secret: 'api-resource-server-secret-key'
  });
})();

// Token Introspection Middleware
async function introspectToken(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing Bearer token.' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const introspection = await client.introspect(token, 'access_token');
    
    if (!introspection.active) {
      return res.status(401).json({ error: 'Token is expired or has been revoked.' });
    }

    // Attach token context claims to request objects
    req.tokenContext = introspection;
    next();
  } catch (err) {
    return res.status(500).json({ error: 'Introspection request failure: ' + err.message });
  }
}

app.get('/protected-data', introspectToken, (req, res) => {
  res.json({
    message: 'Protected resource accessed!',
    scopes: req.tokenContext.scope,
    clientId: req.tokenContext.client_id,
    userSubject: req.tokenContext.sub
  });
});

app.listen(3002, () => console.log('API Resource Server operational at port 3002'));

7. Token Revocation (RFC 7009)

Use Case: Explicitly revoke tokens upon user logout to terminate access instantly.

Complete Node.js Implementation

const { Issuer } = require('openid-client');
const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({ secret: 'revoke-session-secret', resave: false, saveUninitialized: false }));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({ client_id: 'web-portal', client_secret: 'secret123' });
})();

app.get('/logout-revoke', async (req, res) => {
  if (req.session.tokenSet) {
    try {
      const tokenSet = req.session.tokenSet;

      // Revoke access token
      await client.revoke(tokenSet.access_token, 'access_token');
      
      // Revoke refresh token if present
      if (tokenSet.refresh_token) {
        await client.revoke(tokenSet.refresh_token, 'refresh_token');
      }
      console.log('Tokens successfully revoked.');
    } catch (err) {
      console.error('Revocation endpoints returned error:', err.message);
    }
  }

  req.session.destroy(() => {
    res.send('Logged out and tokens revoked successfully.');
  });
});

app.listen(3001);

8. UserInfo Endpoint

Use Case: Query profile details (e.g. firstName, email, custom claims) using an active access token.

Complete Node.js Implementation

const { Issuer } = require('openid-client');
const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({ secret: 'userinfo-session-secret', resave: false, saveUninitialized: false }));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({ client_id: 'web-portal', client_secret: 'secret123' });
})();

app.get('/fetch-profile', async (req, res) => {
  if (!req.session.tokenSet) {
    return res.status(401).send('Not authenticated.');
  }

  try {
    const accessToken = req.session.tokenSet.access_token;
    
    // Call OIDC UserInfo endpoint
    const userInfo = await client.userinfo(accessToken);
    
    res.json({
      message: 'UserInfo retrieved successfully',
      profile: userInfo
    });
  } catch (err) {
    res.status(500).send('UserInfo request failed: ' + err.message);
  }
});

app.listen(3001);

9. RP-Initiated Logout (OIDC Session)

Use Case: Standardized flow to terminate single-sign-on (SSO) sessions on the identity server.

Complete Node.js Implementation

const { Issuer } = require('openid-client');
const express = require('express');
const session = require('express-session');

const app = express();
app.use(session({ secret: 'logout-session-secret', resave: false, saveUninitialized: false }));

let client;
(async () => {
  const issuer = await Issuer.discover('http://localhost:3000/oidc');
  client = new issuer.Client({
    client_id: 'web-portal',
    client_secret: 'secret123',
    post_logout_redirect_uris: ['http://localhost:3001/logout-success']
  });
})();

app.get('/logout', (req, res) => {
  const tokenSet = req.session.tokenSet;
  if (!tokenSet) return res.redirect('/');

  // Generate URL for redirecting user to terminate the SSO session
  const logoutUrl = client.endSessionUrl({
    id_token_hint: tokenSet.id_token, // Recommended to identify session
    post_logout_redirect_uri: 'http://localhost:3001/logout-success'
  });

  // Clear local session state first
  req.session.destroy(() => {
    // Redirect to OIDC provider session termination endpoint
    res.redirect(logoutUrl);
  });
});

app.get('/logout-success', (req, res) => {
  res.send('OIDC Session terminated successfully.');
});

app.listen(3001);

OIDC Series Guides