ENbext.dev
DocumentationOutils pour applications

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Authentification

@bext-stack/framework/auth est le noyau réutilisable du flux d'authentification qu'environ 31 sites bext copient actuellement dans leur propre oidc.ts : analyse de cookies, un cookie de session inviolable signé HMAC et OAuth/OIDC PKCE Aides. C'est la moitié de l'authentification ; Autorisation ( authz ) est la moitié « peuvent-ils le faire ».

Le crypto est pur JavaScript — HMAC-SHA256, SHA-256 et base64url sont implémentés dans le module, validés par rapport aux vecteurs canoniques HMAC et RFC 7636 PKCE — il fonctionne donc de manière identique sur V8 et QuickJS avec non node:crypto ou dépendance WebCrypto (les copies mélangent les deux, ce qui se brise en un pur isolat).

Quand l'utiliser#

  • Un cookie de session signé pour "qui est cette demande" - sans externe magasin de sessions ou une dépendance cryptographique.
  • Lancement d'une connexion OAuth2/OIDC (PKCE + URL d'autorisation).
  • Partout où vous écriviez à la main parseCookies / un signataire HMAC.

Séances signées#

ts
import { createSession } from "@bext-stack/framework/auth";

const session = createSession<{ userId: string; role: string }>({
  secret: process.env.SESSION_SECRET!,   // read from env / a secrets store
  cookie: "sid",
  maxAgeSecs: 86400,                      // expiry is signed INTO the payload
});

// loader — read + verify (tampered or expired → null):
const s = session.read(request);          // { userId, role } | null

// action — issue / clear:
return new Response(null, { status: 303, headers: {
  location: "/",
  "set-cookie": session.cookie({ userId, role }),   // signed Set-Cookie
}});
// logout: session.clearCookie()

Le jeton est base64url(json).signature ; read / verify recalcule le HMAC (temps constant), rejette une charge utile falsifiée et applique le exp intégré à la charge utile — de sorte qu'un jeton copié hors du réseau expire toujours côté serveur, pas seulement dans le Max-Age du navigateur.

Membre Fait
createSession<T>({ secret, cookie?, maxAgeSecs?, sameSite?, secure? }) créer un assistant de session
.read(request) vérifier le cookie → T | null
.issue(data) / .verify(token) jeton ⟷ charge utile
.cookie(data) / .clearCookie() Valeurs d'en-tête Set-Cookie

Cookies et signataire#

ts
import { parseCookies, serializeCookie, createSigner } from "@bext-stack/framework/auth";

parseCookies(request.headers.get("cookie"));         // { name: value, ... }
serializeCookie("flash", "hi", { maxAge: 60 });      // "flash=hi; Path=/; Max-Age=60; SameSite=Lax; HttpOnly; Secure"

const signer = createSigner(secret);                 // low-level HMAC
signer.verify(payload, signer.sign(payload));        // true (constant-time)

OAuth/OIDC (PKCE)#

Assistants indépendants du fournisseur pour le code d'autorisation + flux PKCE :

ts
import { pkceChallenge, buildAuthorizeUrl, randomToken } from "@bext-stack/framework/auth";

const { verifier, challenge } = pkceChallenge();     // stash `verifier` in the session
const state = randomToken();
const url = buildAuthorizeUrl({
  authorizationEndpoint: "https://idp/authorize",
  clientId, redirectUri, scope: "openid email",
  state, codeChallenge: challenge,                    // adds code_challenge_method=S256
});
// redirect the user to `url`; on callback, exchange the code + verifier at the token endpoint.

pkceChallengeFromVerifier(v) et les assistants base64urlEncode / base64urlDecode sont exportés pour créer vous-même l'étape d'échange de jetons.

Conseil

Stockez les verifier et state dans un cookie signé de courte durée (utilisez createSession ) lors de la redirection et vérifiez state lors du rappel : il s'agit de la défense CSRF pour le flux de connexion.

Le client OIDC complet ( createOidcClient )#

Les aides ci-dessus sont les pièces. createOidcClient représente l'ensemble du flux de code d'autorisation (PKCE public) dans un objet configuré — la forme exacte d'environ 30 sites bext utilisés pour copier-coller sous la forme d'un lib/oidc.ts d'environ 500 lignes. Vous lui donnez la configuration ; il restitue PKCE, l'URL d'autorisation, l'échange de jetons, le décodage du jeton d'identification, une enveloppe de flux à cookie unique signée (vérificateur PKCE + état CSRF + chemin de retour dans un cookie signé HMAC), une session signée HMAC et la porte de routage.

ts
import { createOidcClient } from "@bext-stack/framework/oidc";

const client = createOidcClient({
  issuer: "https://auth.example.com",          // default: the bext IdP
  clientId: "my-app",
  redirectUri: "https://my-app.com/auth/callback",
  sessionSecret: () => process.env.SESSION_SECRET,   // string OR lazy resolver
  sessionCookieName: "my_session",
  flowCookieName: "my_flow",
  flowCarrier: "ret",                          // "ret" = 303 back to a path; "popup" = postMessage
});

Coup d'envoi de la connexion : un cookie signé transporte l'intégralité du flux à travers la redirection :

ts
export async function action({ request }) {
  const verifier = client.generateCodeVerifier();
  const challenge = await client.generateCodeChallenge(verifier);
  const state = client.generateState(), nonce = client.generateNonce();
  const flow = client.buildFlowState({ pkce: verifier, state, nonce, ret: "/" });
  return new Response(null, { status: 303, headers: {
    Location: client.buildAuthorizeUrl({ codeChallenge: challenge, state, nonce }),
    "set-cookie": client.flowEnvelopeCookie(flow),
  }});
}

Rappel : vérifiez l'état, échangez le code avec le vérificateur PKCE, signez la session :

ts
export async function loader({ request }) {
  const flow = client.readFlowEnvelope(request);
  const url = new URL(request.url);
  if (!flow || flow.state !== url.searchParams.get("state")) throw fail("state");
  const tok = await client.exchangeCode({ code: url.searchParams.get("code"), codeVerifier: flow.pkce });
  const c = client.decodeIdToken(tok.id_token);
  const now = Math.floor(Date.now() / 1000);
  const session = client.packSession({ sub: c.sub, name: c.name, email: c.email, iat: now, exp: now + 600 });
  const headers = new Headers({ Location: flow.ret });
  headers.append("Set-Cookie", client.sessionCookie(session));
  headers.append("Set-Cookie", client.clearFlowEnvelope());
  throw new Response(null, { status: 303, headers });
}

Lire + portail n'importe quel itinéraire :

ts
const session = client.readSession(request);          // verified payload | null
const gated = client.requireSession(request);         // session | 303 to /auth/login?return=…
if (gated instanceof Response) return gated;
Remarque

createOidcClient est compatible octet pour octet avec les copies manuelles qu’il remplace : même base64url, même enveloppe HMAC portant sur la charge base64url, mêmes attributs des cookies. Le cookie de session est SameSite=Lax; Secure, pas HttpOnly, afin de pouvoir lire le nom d’affichage côté client ; le cookie de flux est HttpOnly; SameSite=None. Un site migré continue de valider les cookies actifs déjà émis, comme vérifié dans oidc.test.ts face à une copie intégrée de l’algorithme exact des sites.

Migration d'un oidc.ts copié-collé#

Remplacez le corps du fichier par une fine cale de réexportation afin que les centaines de Les sites d'appel import … from "../lib/oidc" ne changent jamais :

ts
// src/lib/oidc.ts
import { createOidcClient } from "@bext-stack/framework/oidc";
const client = createOidcClient<Session>({ /* this site's config */ });
export const {
  buildAuthorizeUrl, exchangeCode, decodeIdToken,
  packSession, unpackSession, readSession, sessionCookie, clearSessionCookie,
  buildFlowState, flowEnvelopeCookie, clearFlowEnvelope, readFlowEnvelope,
  requireSession, requireSessionJson, sanitizeReturnPath, parseCookies,
} = client;
export const SESSION_COOKIE_NAME = client.SESSION_COOKIE_NAME;
export interface Session { sub: string; email: string; /* … */ exp: number }
Conseil

Transmettre sessionSecret en tant que fonction lorsque le secret est lu à partir d'un site par site [env] qui se résout paresseusement sur un travailleur à froid : une chaîne capturée se bloquerait undefined. Le résolveur s'exécute à chaque signature/vérification.

Relation avec les capacités d'authentification/session#

Les fonctionnalités Rust Authentification et Session sont des coutures côté serveur. Ce module est le chemin isolé TypeScript qu'une route PRISM utilise directement - le cookie signé réside dans votre code, sans fournisseur à configurer ni dépendance cryptographique à expédier.

Essayez-le#

Le démo de session signée en direct vous connecte avec un cookie inviolable et le vérifie à chaque demande – modifiez le cookie dans DevTools et regardez la session devenir anonyme. Source dans sites/demo/src/app/examples/session/page.tsx.

Le Démo OAuth/OIDC exécute le plein Flux createOidcClient — PKCE, enveloppe de flux signée, échange de jetons, session signée — par rapport à un IdP fictif sur le même site. Source dans sites/demo/src/app/examples/oauth-mock/.

Voir aussi#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.