What happens
The result of callback is { tokens, claims, userinfo, user, returnTo }.
authorizecreates astate, anonceand a PKCE verifier (32, 32 and 48 random bytes), stores them in the session for ten minutes, and redirects to the provider with the S256 challenge. Up to eight sign-ins can wait in one session (several tabs); expired ones are dropped.- The visitor signs in at the provider, which redirects back to your callback with
code,stateandiss. callbackremoves the waiting sign-in that matchesstate(so the URL works once, even when a later step fails), checks the age and theissparameter, exchanges the code with the verifier, and verifies the ID token.signInputs the user (and, unlessstoreTokens: false, the tokens) in the session under a new session id.
The result of callback is { tokens, claims, userinfo, user, returnTo }. user is { id, issuer, name?, email?, emailVerified?, picture?, username? }: id is the sub claim, unique only together with issuer, so key your own user table
on both. Do not link two accounts by e-mail unless emailVerified is true.
To control the steps yourself, call callback and signIn instead of handleCallback; between them you can look the user
up, create the account, or refuse the sign-in:
src/routes/auth/callback-custom.tsts
import { OidcError } from "@shadow-js/dusk/auth";
import { HttpError, type RequestEvent } from "@shadow-js/dusk/server";
import { oidc } from "../../auth";
export const GET = async (event: RequestEvent): Promise<Response> => {
const result = await oidc.callback(event);
if (result.user.emailVerified !== true || !result.user.email?.endsWith("@example.com")) {
throw new HttpError(403, "Only verified example.com addresses may sign in.");
}
await oidc.signIn(event, result);
return new Response(null, { status: 303, headers: { location: result.returnTo } });
};
export const isRefused = (error: unknown): boolean =>
error instanceof OidcError && error.code === "OIDC_AUTHORIZATION_ERROR";