Aller au contenu principal

Authentification — Auth0

Auth0 est le fournisseur d'identité (OAuth 2.0 + OpenID Connect). Point crucial : il n'y a pas de table users. L'API vérifie le JWT à chaque requête et indexe tout sur le sub Auth0.

La frontière (port auth)

Comme tout SDK tiers, Auth0 vit derrière un port dans packages/lib :

  • packages/lib/src/auth/port.ts — interface IdentityDirectory + types domaine.
  • packages/lib/src/auth/adapter.auth0.ts — implémentation Auth0 : JWKS + Management API (refresh de profil, liste paginée, suspension).

Le middleware API (apps/api/src/middleware/) consomme le port, extrait le sub et le pose dans c.var.user. Le profil complet (nom, email, avatar) est récupéré à la demande via la Management API et mis en cache Redis.

Interface IdentityDirectory

interface IdentityDirectory {
verifyJwt(args: { token: string }): Promise<VerifiedJwt>
getUser(args: { sub: string }): Promise<User | null>
listUsers(args: { page?: number; perPage?: number; query?: string }): Promise<ListUsersResult>
setUserBlocked(args: { sub: string; blocked: boolean }): Promise<void>
}

User expose les champs optionnels blocked et lastLogin (renseignés par Auth0 ; absents sur les entrées qui ne les ont pas). ListUsersResult vaut { users: User[]; total: number }.

Middleware admin

adminMiddleware protège les routes /admin/*. Sa signature est :

adminMiddleware(args: { env: Env; auth: IdentityDirectory })

Il doit être monté après authMiddleware (qui pose c.var.user). La résolution de l'email suit deux étapes :

  1. Depuis le token JWTc.var.user.email si présent (les access tokens Auth0 avec audience = l'API n'incluent pas le claim email par défaut, donc ce champ est souvent absent).
  2. Depuis la Management API — si l'email est absent du token, le middleware appelle args.auth.getUser({ sub }) (résultat mis en cache par IdentityDirectory) pour le résoudre.

Retourne 403 si aucun email ne peut être résolu ou s'il n'est pas dans l'allowlist ADMIN_EMAILS.

authMiddleware → adminMiddleware → handler

parseAdminEmails(raw) (dans apps/api/src/env.ts) normalise la chaîne en un Set<string> bas-de-casse sans espaces.

Côté web — la frontière client

apps/web/middleware.ts combine Auth0 (sur /auth/*, /app/*) et next-intl (routes publiques). Règle stricte : les Server Components / Server Actions appellent getEnv() puis passent des valeurs concrètes en props aux Client Components — on n'importe jamais env.ts depuis un fichier "use client" (le garde server-only casserait au build). Voir Conventions → Env.

Tokens d'email (claim / tracking)

Les liens envoyés par email (claim d'une fiche, suivi d'un tracking) n'utilisent pas Auth0 : ils portent un JWT signé séparément (HS256, CLAIM_SECRET). La signature est vérifiée sans création de compte. C'est ce qui permet de notifier un prestataire scrapé avant même qu'il ait un compte. Voir Cold start & claim.