How to Use Authorization Code Flow with Fabrixly-IDS

Overview

The Authorization Code Flow is the most secure and recommended flow for web applications with a backend. It exchanges an authorization code for tokens, keeping the client secret secure.

Prerequisites

  1. Fabrixly-IDS instance running
  2. OIDC client registered in Fabrixly-IDS
  3. Node.js (for example code)

Step 1: Register Your OIDC Client

First, create a client in Fabrixly-IDS via the Admin Console:

  1. Go to Clients > Create Client
  2. Save the client and note down the client_id and client_secret

Fill in these details:

{
  "client_name": "My Web App",
  "redirect_uris": ["http://localhost:3001/callback"],
  "response_types": ["code"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "client_secret_basic",
  "auth_mode": "flexible",
  "enable_mobile_login": false,
  "otp_delivery_method": "email",
  "requires_consent": false
}

Step 2: Configure Your App

Install dependencies:

npm install openid-client express express-session

Create app.js:

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

const app = express();
const PORT = 3001;

app.use(session({
  secret: 'your-session-secret-keep-it-safe',
  resave: false,
  saveUninitialized: true
}));

// Step 3: Discover the Fabrixly-IDS Issuer
async function initClient() {
  const issuer = await Issuer.discover('https://ids.fabrixly.com/oidc');
  return new issuer.Client({
    client_id: 'your-client-id',
    client_secret: 'your-client-secret',
    redirect_uris: ['http://localhost:3001/callback'],
    response_types: ['code']
  });
}

let client;
initClient().then(c => client = c);

// Step 4: Redirect to Fabrixly-IDS for Login
app.get('/login', async (req, res) => {
  const codeVerifier = generators.codeVerifier();
  const codeChallenge = generators.codeChallenge(codeVerifier);
  
  // Store verifier in session
  req.session.codeVerifier = codeVerifier;
  req.session.state = generators.state();
  req.session.nonce = generators.nonce();
  
  const authUrl = client.authorizationUrl({
    scope: 'openid profile email offline_access',
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state: req.session.state,
    nonce: req.session.nonce
  });
  
  res.redirect(authUrl);
});

// Step 5: Handle Callback and Exchange Code for Tokens
app.get('/callback', async (req, res) => {
  const params = client.callbackParams(req);
  
  const tokenSet = await client.callback(
    'http://localhost:3001/callback',
    params,
    {
      code_verifier: req.session.codeVerifier,
      state: req.session.state,
      nonce: req.session.nonce
    }
  );
  
  // Store tokens in session
  req.session.tokenSet = tokenSet;
  
  // Get user info
  const userInfo = await client.userinfo(tokenSet.access_token);
  req.session.user = userInfo;
  
  res.redirect('/dashboard');
});

// Step 6: Protected Route Using Access Token
app.get('/dashboard', (req, res) => {
  if (!req.session.user) return res.redirect('/login');
  
  res.send(`
    <h1>Welcome, ${req.session.user.name}</h1>
    <p>Email: ${req.session.user.email}</p>
    <a href="/logout">Logout</a>
  `);
});

// Step 7: Logout
app.get('/logout', async (req, res) => {
  if (req.session.tokenSet) {
    try {
      // Revoke tokens
      if (req.session.tokenSet.access_token) {
        await client.revoke(req.session.tokenSet.access_token);
      }
      if (req.session.tokenSet.refresh_token) {
        await client.revoke(req.session.tokenSet.refresh_token);
      }
    } catch (err) {
      console.error('Error revoking tokens:', err);
    }
  }
  
  req.session.destroy();
  res.redirect('/');
});

app.listen(PORT, () => {
  console.log(`App running on http://localhost:${PORT}`);
});

Step 8: Run the App

node app.js

Visit http://localhost:3001/login to test!

Using PKCE for Public Clients

For mobile or single-page apps (SPA), use PKCE (Proof Key for Code Exchange) which doesn't require a client secret:

// Configure client without client_secret
const client = new issuer.Client({
  client_id: 'your-public-client-id',
  redirect_uris: ['http://localhost:3001/callback'],
  response_types: ['code'],
  token_endpoint_auth_method: 'none'
});

// Use PKCE code verifier and challenge
// Same as Step 4

Token Refresh

Use refresh tokens to get new access tokens without user interaction:

// In your app, check if token is expired
if (req.session.tokenSet.expired()) {
  const newTokenSet = await client.refresh(req.session.tokenSet.refresh_token);
  req.session.tokenSet = newTokenSet;
}

API Reference

  • Discovery URL: https://ids.fabrixly.com/oidc/.well-known/openid-configuration
  • Authorization Endpoint: /oidc/auth
  • Token Endpoint: /oidc/token
  • UserInfo Endpoint: /oidc/me
  • Token Introspection: /oidc/token/introspection
  • Token Revocation: /oidc/token/revocation
  • End Session: /oidc/session/end

Best Practices

  1. Always use HTTPS in production
  2. Store refresh tokens securely
  3. Rotate refresh tokens (enabled by default in Fabrixly-IDS)
  4. Validate state and nonce to prevent CSRF
  5. Use short-lived access tokens

Subscribe to The Fabrixly Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe