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.
When to choose which
Section titled “When to choose which”- Embedded window (widget) — the user does not leave your page, the window
is rendered on top. It needs
sdk.jsand 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.
How sign-in goes
Section titled “How sign-in goes”-
Send the user to
/oauth/authorizewith the query parameters. Generate PKCE,stateandnonce, and store them in the session. -
The user signs in on our page and returns to your
redirect_uriwithcodeandstatein the query. -
Exchange the code for a token and verify the
id_token— with the same code as in the backend example.
The code
Section titled “The code”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.