UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

106 lines (98 loc) 3.43 kB
import { $context, AlephaError, z } from "alepha"; import type { IssuerPrimitive } from "alepha/security"; import { $auth, type LinkAccountFn, type OidcOptions, type WithLinkFn, } from "./$auth.ts"; /** * Creates an authentication provider primitive for France Connect. * * Uses OpenID Connect (OIDC) to authenticate users via France Connect, * the French government's identity federation system. It provides verified * identity data (name, email, birthdate) sourced directly from government * databases. * * **France Connect-specific behaviour**: * - Scopes use individual claim names (`given_name`, `family_name`) rather * than the standard grouped `profile` scope. * - The `acr_values=eidas1` authorization parameter is mandatory and is * included automatically. * - Logout is mandatory in France Connect integrations. Store the `id_token` * returned at login and pass it to the logout endpoint when the session ends. * * **Environment Variables** (obtain from partenaires.franceconnect.gouv.fr): * - `FRANCECONNECT_CLIENT_ID` — OAuth 2.0 client ID for your France Connect service provider. * - `FRANCECONNECT_CLIENT_SECRET` — OAuth 2.0 client secret for your France Connect service provider. * * @example * ```ts * class AuthProviders { * franceconnect = $authFranceConnect(this.userRealm); * } * ``` */ export const $authFranceConnect = ( realm: IssuerPrimitive & WithLinkFn, options: Partial<OidcOptions> = {}, ) => { const { alepha } = $context(); const env = alepha.parseEnv( z.object({ FRANCECONNECT_CLIENT_ID: z .text({ description: "The OAuth 2.0 client ID for your France Connect service provider, obtained from partenaires.franceconnect.gouv.fr.", }) .optional(), FRANCECONNECT_CLIENT_SECRET: z .text({ description: "The OAuth 2.0 client secret for your France Connect service provider, obtained from partenaires.franceconnect.gouv.fr.", }) .optional(), }), ); const disabled = !env.FRANCECONNECT_CLIENT_ID || !env.FRANCECONNECT_CLIENT_SECRET; const name = "franceconnect"; const account: LinkAccountFn | undefined = options.account ?? (realm.link ? realm.link(name) : undefined); if (!account) { throw new AlephaError( "Authentication requires a link function in the realm primitive.", ); } return $auth({ issuer: realm, name, oidc: { /** * France Connect production OIDC issuer. * Discovery: https://oidc.franceconnect.gouv.fr/api/v2/.well-known/openid-configuration * * Note: `oidc.franceconnect.gouv.fr` is standard FranceConnect (eidas1). * `auth.franceconnect.gouv.fr` is FranceConnect+ (eidas2/eidas3). */ issuer: "https://oidc.franceconnect.gouv.fr/api/v2", clientId: env.FRANCECONNECT_CLIENT_ID!, clientSecret: env.FRANCECONNECT_CLIENT_SECRET, /** * France Connect requires individual claim names as scopes. * The standard grouped `profile` scope is NOT supported. */ scope: "openid given_name family_name email", /** * `acr_values=eidas1` is mandatory for all France Connect integrations. */ ...options, authorizationParameters: { acr_values: "eidas1", ...options.authorizationParameters, }, account, }, disabled, }); };