Node.js without a framework

The authorization flow on openid-client, when Auth.js does not fit.

npm install openid-client

Setup

import * as client from 'openid-client'

const config = await client.discovery(
  new URL('https://auth.my'),
  process.env.AUTHMY_CLIENT_ID,
  process.env.AUTHMY_CLIENT_SECRET,
)

One call fetches endpoints, keys and algorithms. You do not list them yourself.

Send the user to sign in

const codeVerifier = client.randomPKCECodeVerifier()
const state = client.randomState()
const nonce = client.randomNonce()

const url = client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://example.com/callback',
  scope: 'openid profile email',
  code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
  code_challenge_method: 'S256',
  state,
  nonce,
})
Important

codeVerifier, state and nonce must survive between the two requests — in a server-side session or a signed cookie. Without state you cannot tell your own user's return from one planted by someone else.

Handle the return

const tokens = await client.authorizationCodeGrant(config, currentUrl, {
  pkceCodeVerifier: codeVerifier,
  expectedState: state,
  expectedNonce: nonce,
})

const claims = tokens.claims()
claims.sub            // permanent identifier — store this one
claims.email          // the profile is inside the id_token
tokens.access_token   // for requests to /me
tokens.refresh_token  // issued without asking

Signature, expiry, issuer and nonce are all verified by the library inside authorizationCodeGrant.

Fresh profile data

const profile = await client.fetchUserInfo(config, tokens.access_token, claims.sub)

Only needed if the data may have changed since sign-in: name and email already arrived in the id_token.

Refresh

const fresh = await client.refreshTokenGrant(config, tokens.refresh_token)

The response carries a new refresh token. Store it in place of the old one — details and pitfalls in Tokens.

Did this page help?