Documentation

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 :

  1. Génère 32 bytes random → hex → préfixe sk_
  2. Stocke sha256(rawKey) dans tokenHash
  3. Stocke les 8 premiers chars dans prefix (pour reconnaissance UI)
  4. Affiche la clé brute UNE SEULE FOIS côté client (toast + bouton "Copier")
  5. 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 :

  1. Lit le header
  2. Hash le token → match contre ApiKey.tokenHash
  3. Vérifie revokedAt = null
  4. Update lastUsedAt = now() (best effort, pas dans la hot path)
  5. 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 en findUnique({ where: { tokenHash } }) (index unique)
  • Clé révoquée garde son historique mais ne peut plus auth
  • Jamais loggée en prod (cf. lib/audit.ts ne 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 :

  1. Ajoute scopes String[] @default([]) sur ApiKey
  2. À la création, demande à l'user de cocher des scopes (read:orders, write:billing, etc.)
  3. Dans authenticateApiKey, retourne aussi la scope list
  4. 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.

On this page