OAuth and OIDC

OAuth 2.0 and OpenID Connect Sign-In

OAuth 2.0 is an authorization protocol: it gets your server an access token for somebody else's API. OpenID Connect is a thin layer on top that also answers "who is this person" by returning an ID token, a JWT of verified identity claims. "Sign in with Google" is OpenID Connect; reading a user's repositories is OAuth.

The authorization code flow with PKCE
The authorization code flow with PKCE

Only the authorization code flow is current. The implicit flow returned tokens in the URL fragment and is retired; so is the password grant. PKCE (RFC 7636) — a random code_verifier, its SHA-256 sent ahead as code_challenge — was once a mobile-only add-on, and the OAuth 2.0 Security Best Current Practice now requires it for every client.

Sign-in with openid-clientJavaScript
import * as client from 'openid-client';        // npm i openid-client@6.8.8
const config = await client.discovery(new URL(ISSUER + '/.well-known/openid-configuration'),
  process.env.CLIENT_ID, process.env.CLIENT_SECRET);
app.get('/auth/login', async (req, res) => {
  const verifier = client.randomPKCECodeVerifier();
  req.session.pkce = verifier;                          // never send this to the browser
  req.session.state = client.randomState();
  res.redirect(client.buildAuthorizationUrl(config, {
    redirect_uri: 'https://app.example.com/auth/callback',
    scope: 'openid email profile', state: req.session.state,
    code_challenge: await client.calculatePKCECodeChallenge(verifier),
    code_challenge_method: 'S256'
  }).href);
});
app.get('/auth/callback', async (req, res) => {
  const tokens = await client.authorizationCodeGrant(config, new URL(req.url, BASE),
    { pkceCodeVerifier: req.session.pkce, expectedState: req.session.state });
  const claims = tokens.claims();                       // signature, iss, aud, exp checked
  const user = await users.upsertByIssuer(claims.iss, claims.sub, claims.email);
  req.session.regenerate(() => { req.session.userId = user.id; res.redirect('/'); });
});   // a failed state or PKCE check throws here, and Express 5 sends it to your handler

The state parameter is the CSRF defense for the redirect: without comparing it, an attacker can feed your callback a code from their account and silently link it to your user's session. Never treat claims.email as a primary key either — the stable identity is the pair (iss, sub).

Prefer a maintained library to hand-rolled requests: openid-client handles discovery, JWKS fetching and key rotation, and ID token validation. Either way, the session you create at the end is an ordinary Express 24,430 session.