Skip to content
FidmosDocumentation

Redirect login

Besides the embedded window there is the classic OAuth 2.0 path: the browser goes to our sign-in page, the user signs in and comes back to you with a code. No widget is needed for this.

  • Embedded window (widget) — the user does not leave your page, the window is rendered on top. It needs sdk.js and allowed origins.
  • Authorization Code Flow (redirect) — this page; a full redirect to our page and back. Simpler for server-side apps without an SPA, works without a sign-in widget on the page.

Both forms end the same way: an authorization code on your backend that you exchange for a token.

  1. Send the user to /oauth/authorize with the query parameters. Generate PKCE, state and nonce, and store them in the session.

  2. The user signs in on our page and returns to your redirect_uri with code and state in the query.

  3. Exchange the code for a token and verify the id_token — with the same code as in the backend example.

import * as client from 'openid-client';
import { ISSUER, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from './config.js';

const config = await client.discovery(new URL(ISSUER), CLIENT_ID, CLIENT_SECRET);
const pending = new Map(); // in production — a session/Redis, keyed by your session id

// Step 1. Build the authorization URL and send the browser there.
export async function buildLoginUrl() {
  const codeVerifier = client.randomPKCECodeVerifier();
  const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
  const state = client.randomState();
  const nonce = client.randomNonce();

  const url = client.buildAuthorizationUrl(config, {
    redirect_uri: REDIRECT_URI,
    scope: 'openid profile email',
    state,
    nonce,
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
  });

  pending.set(state, { codeVerifier, nonce });
  return url.href; // do res.redirect(url.href)
}

// Step 2. Handle the return: the request URL contains code and state.
export async function handleCallback(incomingUrl) {
  const url = new URL(incomingUrl);
  const draft = pending.get(url.searchParams.get('state'));

  const tokens = await client.authorizationCodeGrant(config, url, {
    pkceCodeVerifier: draft.codeVerifier,
    expectedState: url.searchParams.get('state'),
    expectedNonce: draft.nonce,
    idTokenExpected: true,
  });

  return tokens.claims().sub; // pairwise user identifier
}

buildAuthorizationUrl sets response_type=code itself; we compute code_challenge from code_verifier and pass it explicitly. The library takes endpoint addresses from discovery — no need to hardcode them.

Next — user profile and error codes.