← Back to Blog

Getting Started with OIDC Integration

The absolute developer guide to integrating OpenID Connect and OAuth 2.0 in Node.js applications

This guide provides complete, production-ready implementation snippets for setting up standard OpenID Connect (OIDC) authentication. Zero IDS exposes standard OIDC discovery endpoints, allowing compatibility with standard client libraries.

1 Install the OpenID Client and Express Dependencies

We use the official, certified openid-client package alongside express-session to manage state, tokens, and verification:

npm install openid-client express express-session
2 Application Configuration & Provider Discovery

Initialize OIDC using discovery. The discovery endpoint fetches OIDC metadata dynamically (authorization endpoint, token endpoint, JWKS URI, scopes supported):

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

const app = express();

// Configure session storage. Use memory store only for development.
// In production, configure RedisStore or MongoStore for persistence and scaling.
app.use(session({
    secret: 'a-secure-random-session-secret-key-12345',
    resave: false,
    saveUninitialized: false,
    cookie: {
        httpOnly: true, // Prevents XSS attacks from reading session ID
        secure: false,  // Set to true in production with HTTPS
        sameSite: 'lax'  // Protects against CSRF attacks
    }
}));

// Global client container
let client;

// Discover OIDC server parameters dynamically
async function initOidc() {
    try {
        const issuer = await Issuer.discover('https://ids.fabrixly.com/oidc');
        console.log('Discovered issuer metadata:', issuer.metadata.issuer);
        
        client = new issuer.Client({
            client_id: 'your-client-id',
            client_secret: 'your-client-secret',
            redirect_uris: ['http://localhost:3001/callback'],
            response_types: ['code']
        });
    } catch (err) {
        console.error('OIDC Discovery failed:', err);
    }
}
initOidc();
💡 Pro-Tip: The Issuer.discover method requests https://ids.fabrixly.com/oidc/.well-known/openid-configuration. This configuration document ensures your app automatically syncs configuration changes (like token signing key rotation).
3 Implement Login & Authorization Handlers (HTTP Wire Example)

Create routes for login redirects and exchange callbacks. Below is the code implementation followed by the actual HTTP request/response wire representations:

// 1. Initiate authorization redirect
app.get('/login', (req, res) => {
    // Generate PKCE code verifier and code challenge
    const code_verifier = generators.codeVerifier();
    const code_challenge = generators.codeChallenge(code_verifier);
    
    // Generate secure state to prevent Cross-Site Request Forgery (CSRF)
    const state = generators.state();
    const nonce = generators.nonce();

    // Persist values in user session for validation upon callback redirect
    req.session.code_verifier = code_verifier;
    req.session.state = state;
    req.session.nonce = nonce;

    const authUrl = client.authorizationUrl({
        scope: 'openid profile email offline_access', // Include offline_access to receive refresh tokens
        state: state,
        nonce: nonce,
        code_challenge: code_challenge,
        code_challenge_method: 'S256'
    });
    
    res.redirect(authUrl);
});

// 2. Process OIDC callback response redirect
app.get('/callback', async (req, res) => {
    const params = client.callbackParams(req);
    
    try {
        // Exchange authorization code for TokenSet
        const tokenSet = await client.callback(
            'http://localhost:3001/callback',
            params,
            {
                code_verifier: req.session.code_verifier,
                state: req.session.state,
                nonce: req.session.nonce
            }
        );
        
        // Save the resulting tokens in the session
        req.session.tokenSet = tokenSet;
        
        // Retrieve profile details using the UserInfo endpoint
        req.session.userInfo = await client.userinfo(tokenSet);
        
        res.redirect('/profile');
    } catch (err) {
        console.error('Authorization Callback Exchange failure:', err);
        res.status(500).send('Authentication failed: ' + err.message);
    }
});

HTTP Request/Response Under-the-Hood

Redirection to Authorization endpoint:

GET /auth?response_type=code&client_id=web-portal&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fcallback&scope=openid%20profile%20email&state=abc123state&code_challenge=xyzChallenge&code_challenge_method=S256 HTTP/1.1
Host: localhost:3000

Redirect Response from Authorization endpoint:

HTTP/1.1 302 Found
Location: http://localhost:3001/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=abc123state

Back-channel Token Exchange request:

POST /token HTTP/1.1
Host: localhost:3000
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fcallback&client_id=web-portal&client_secret=secret123&code_verifier=highEntropyVerifier

Token Response Payload:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "def50200b1...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email"
}
4 Protect Middleware & Secure API Access

Protect server-side endpoints with middleware. The middleware validates token state and handles automatic refreshing:

// Middleware asserting active token presence
async function requireAuth(req, res, next) {
    if (!req.session.tokenSet) {
        return res.redirect('/login');
    }
    
    // Check if token has expired or is close to expiration
    let tokenSet = new client.issuer.TokenSet(req.session.tokenSet);
    if (tokenSet.expired()) {
        try {
            console.log('Token expired. Attempting refresh token grant...');
            const refreshed = await client.refresh(tokenSet.refresh_token);
            req.session.tokenSet = refreshed;
            tokenSet = refreshed;
        } catch (err) {
            console.error('Refresh Token validation failed. Requesting re-authorization:', err);
            return res.redirect('/login');
        }
    }
    
    req.tokenSet = tokenSet;
    next();
}

// Protected Resource Endpoint
app.get('/profile', requireAuth, (req, res) => {
    res.json({
        message: 'Resource accessed successfully',
        user: req.session.userInfo,
        claims: req.tokenSet.claims() // Parsed ID token claims
    });
});

// Logout Handler
app.get('/logout', async (req, res) => {
    const tokenSet = req.session.tokenSet;
    
    req.session.destroy(() => {
        if (tokenSet && tokenSet.id_token) {
            // Initiate OIDC RP-Initiated logout to clear SSO session on Identity Server
            const logoutUrl = client.endSessionUrl({
                id_token_hint: tokenSet.id_token,
                post_logout_redirect_uri: 'http://localhost:3001/'
            });
            return res.redirect(logoutUrl);
        }
        res.redirect('/');
    });
});
5 Complete Express Server Bootup
app.listen(3001, () => {
    console.log('Service operational at http://localhost:3001');
    console.log('Initiate flow at http://localhost:3001/login');
});

OIDC Integration Developer Path