Skip to content
Docs · Any OIDC library

Quickstart: any OIDC library

Usher is a standard OpenID Connect provider. If your language has a certified OIDC client, you already have everything you need: point it at your environment’s discovery document and use the authorisation code flow with PKCE.

The settings

SettingValue
Issuerhttps://<handle>.id.hallelo.net
Discovery URLhttps://<handle>.id.hallelo.net/.well-known/openid-configuration
FlowAuthorisation code (response_type=code)
PKCERequired, code_challenge_method=S256
Scopesopenid profile email offline_access
Client authenticationAs listed in token_endpoint_auth_methods_supported

Your environment’s handle is in Usher Console, beside its issuer. Development environments get a random one, such as wise-otter-42; production environments have the handle you chose. A production environment can live at your own domain instead, such as login.acme.com: then that is its issuer, once you have pointed the domain at Usher with a CNAME record.

Read the discovery document

Every endpoint and key your client needs is listed here. Let your library read it rather than copying URLs into your code.

Terminal
curl -s https://acme.id.hallelo.net/.well-known/openid-configuration | jq
Response (abridged)
{
  "issuer": "https://acme.id.hallelo.net",
  "authorization_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/authorize",
  "token_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/token",
  "userinfo_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/userinfo",
  "jwks_uri": "https://acme.id.hallelo.net/api/auth/jwks",
  "introspection_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/introspect",
  "revocation_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/revoke",
  "end_session_endpoint": "https://acme.id.hallelo.net/api/auth/oauth2/end-session",
  "scopes_supported": ["openid", "profile", "email", "offline_access"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
  "code_challenge_methods_supported": ["S256"]
}

The flow

Send the person to the authorisation endpoint

Make a random PKCE verifier and keep it, with a random state, somewhere only this browser’s session can reach (a short-lived, httpOnly cookie works well). Then redirect with response_type=code, your client_id and redirect_uri, the scopes above, the state, and the S256 code_challenge of your verifier.

Let them sign in

The Lobby takes it from here: it names your app, offers the sign-in methods your environment allows, and asks for consent the first time.

Check the callback

Usher redirects to your redirect_uri with code and state. Refuse the response unless state matches the one you stored. The code is single-use and lasts five minutes.

Swap the code for tokens

From your server, post the code and the PKCE verifier to the token endpoint:

Token request
curl -s -X POST https://acme.id.hallelo.net/api/auth/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://app.acme.example/callback \
-d code_verifier="$CODE_VERIFIER"

Validate the ID token

Check its signature against the keys at jwks_uri, and that iss is your issuer, aud is your client ID and exp is in the future. Your library does this for you; make sure it isn’t turned off.

In Node.js

openid-client does all of the above, including validation:

openid-client
import * as client from 'openid-client'

// discovery: every endpoint and key comes from the issuer
const config = await client.discovery(
  new URL('https://acme.id.hallelo.net'),
  process.env.USHER_CLIENT_ID!,
  process.env.USHER_CLIENT_SECRET!,
)

// 1. send them to the Lobby, with PKCE (S256) and a state to check later
const codeVerifier = client.randomPKCECodeVerifier()
const state = client.randomState()
const url = client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://app.acme.example/callback',
  scope: 'openid profile email offline_access',
  code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
  code_challenge_method: 'S256',
  state,
})
// keep codeVerifier and state in a short-lived httpOnly cookie, then redirect to url

// 2. back at /callback: swap the code for tokens
const tokens = await client.authorizationCodeGrant(config, new URL(request.url), {
  pkceCodeVerifier: codeVerifier,
  expectedState: state,
})
const { sub, email, name } = tokens.claims()!

In other languages

These widely used libraries all speak standard OpenID Connect and work with Usher without anything Usher-specific:

LanguageLibrary
PythonAuthlib
Gocoreos/go-oidc with golang.org/x/oauth2
JavaSpring Security OAuth 2.0 Client
.NETMicrosoft.AspNetCore.Authentication.OpenIdConnect
Rubyomniauth_openid_connect
PHPjumbojett/openid-connect-php

Staying signed in

With the offline_access scope, the token response includes a refresh token. Use it with grant_type=refresh_token to get a new access token when the old one expires. Refresh tokens rotate: each use returns a new one, and the old one stops working.

Signing out

Clear your own session first, then redirect to the end_session_endpoint with post_logout_redirect_uri (and id_token_hint if you have it), so the Lobby forgets the person too.