Clés API
Génération de clés sk_xxx pour usage programmatique — hashées en DB, affichées une seule fois, helper d'auth serveur.
Permet aux users d'authentifier des appels HTTP programmatiques (scripts, intégrations tierces) sans exposer leur mot de passe ni JWT de session.
Page : /account/api-keys. Modèle ApiKey.
Modèle de données
model ApiKey {
id String @id @default(cuid())
userId String
name String // libellé user-friendly
prefix String // "sk_xxxxxxxx" (8 chars affichés en liste)
tokenHash String @unique // sha256 du token complet
createdAt DateTime @default(now())
lastUsedAt DateTime?
revokedAt DateTime?
@@index([userId])
}Format des clés
sk_<32 hex chars>À la création :
- Génère 32 bytes random → hex → préfixe
sk_ - Stocke
sha256(rawKey)danstokenHash - Stocke les 8 premiers chars dans
prefix(pour reconnaissance UI) - Affiche la clé brute UNE SEULE FOIS côté client (toast + bouton "Copier")
- Après ça, plus jamais — l'user doit en créer une nouvelle s'il l'a perdue
Helper : lib/api-key-auth.ts → generateApiKey().
Authentifier une requête
Côté server / route API :
import { authenticateApiKey } from "@/lib/api-key-auth";
export async function GET(req: Request) {
const user = await authenticateApiKey(req);
if (!user) return new Response("Unauthorized", { status: 401 });
// user.id, user.email, etc.
}Headers acceptés :
Authorization: Bearer sk_xxxxxxxx...authenticateApiKey :
- Lit le header
- Hash le token → match contre
ApiKey.tokenHash - Vérifie
revokedAt = null - Update
lastUsedAt = now()(best effort, pas dans la hot path) - Renvoie le user owner
UI
/account/api-keys :
- Liste des clés avec
name,prefix(sk_a3b8f1...),lastUsedAt,createdAt, statut (active / revoked) - Bouton "Nouvelle clé" → modal pour le name → toast avec la clé brute
- Bouton "Révoquer" par row → set
revokedAt = now()(pas de delete, on garde l'historique)
Sécurité
- Jamais de clé en clair en DB — uniquement le hash
- Pas de pagination cursor sur
tokenHash— la lookup est enfindUnique({ where: { tokenHash } })(index unique) - Clé révoquée garde son historique mais ne peut plus auth
- Jamais loggée en prod (cf.
lib/audit.tsne stocke jamais les clés ni leurs préfixes)
Audit
apikey.create (à la création), apikey.revoke (à la révocation).
Visible dans le user activity log via filter apikey.*.
Tests
Couvert par lib/api-key-auth.test.ts (génération, hash, auth header
parsing, revocation).
Étendre — scopes
Le boilerplate ship sans scopes (une clé valide = full access du user). Pour ajouter une scope :
- Ajoute
scopes String[] @default([])surApiKey - À la création, demande à l'user de cocher des scopes (
read:orders,write:billing, etc.) - Dans
authenticateApiKey, retourne aussi la scope list - Chaque route check
if (!apiKey.scopes.includes("read:orders")) return 403
Pas de oauth-style consent flow — c'est juste un toggle côté user à la création.