Thoryn

Quickstarts · express

Express — Hub login with `openid-client`

Add OIDC to an Express 4 / 5 app using the official Node OIDC library. ~40 lines of code.

Tested against:framework: Express 5.0openidClient: openid-client@5.6

Express + Thoryn quickstart architecture — passport with the openid-client strategy routes through Hub to your federation member; req.user is the authenticated profile

Prereqs

  • Node 20+
  • Express 4 or 5

Step 1 — Register a confidential client

hub clients create \
  --name "My Express app" \
  --redirect-uri "http://localhost:3000/auth/callback" \
  --grant-types authorization_code,refresh_token \
  --scopes "openid email profile"

Step 2 — Install

npm install express express-session openid-client

Step 3 — Wire it

server.mjs:

import express from "express";
import session from "express-session";
import { Issuer, generators } from "openid-client";
 
const app = express();
app.use(session({ secret: "dev", resave: false, saveUninitialized: false }));
 
const issuer = await Issuer.discover("https://hub.thoryn.org");
const client = new issuer.Client({
  client_id: process.env.THORYN_CLIENT_ID,
  client_secret: process.env.THORYN_CLIENT_SECRET,
  redirect_uris: ["http://localhost:3000/auth/callback"],
  response_types: ["code"],
});
 
app.get("/login", (req, res) => {
  const codeVerifier = generators.codeVerifier();
  const codeChallenge = generators.codeChallenge(codeVerifier);
  req.session.codeVerifier = codeVerifier;
  res.redirect(client.authorizationUrl({
    scope: "openid email profile",
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
  }));
});
 
app.get("/auth/callback", async (req, res) => {
  const params = client.callbackParams(req);
  const tokenSet = await client.callback(
    "http://localhost:3000/auth/callback",
    params,
    { code_verifier: req.session.codeVerifier },
  );
  req.session.user = tokenSet.claims();
  res.redirect("/");
});
 
app.get("/", (req, res) => {
  if (!req.session.user) return res.send(`<a href="/login">Sign in</a>`);
  res.send(`<p>Hello, ${req.session.user.name}</p>`);
});
 
app.listen(3000);

Step 4 — Run it

THORYN_CLIENT_ID=... THORYN_CLIENT_SECRET=... node server.mjs

What's next

Troubleshooting

  • code_verifier mismatch: check that the same session is reused across /login and /auth/callback. If you're behind a reverse proxy, fix trust proxy.
  • Issuer.discover hangs: corporate proxies sometimes block OIDC discovery. Set HTTP_PROXY / HTTPS_PROXY.