Apogée Consult
Retour au blog
Mathieu Ponton
Mathieu PontonCo-Founder & ingénieur logiciel

Je suis Mathieu Ponton, Co-Founder & ingénieur logiciel chez Apogée Consult à Lyon. Ingénieur diplômé de Polytech Lyon (Informatique), j'ai fait trois ans en apprentissage, partagés entre la Métropole de Lyon (inclusion numérique avec Res'in et sobriété énergétique avec Écolyo) et Superwyze, une startup medtech (POCs, dont certains aujourd'hui industrialisés, et travail sur des codebases existantes). J'ai livré plus de 10 projets en production (web, mobile et IA / RAG) pour des PME, startups et organisations publiques.

Pourquoi on n'utilise plus NextAuth pour nos nouveaux projets

  • secu

Auth.js a évolué, mais ses abstractions nous ont coûté cher. On explique pourquoi on a arrêté de l'utiliser et ce qu'on lui préfère selon le contexte.

Pourquoi on n'utilise plus NextAuth pour nos nouveaux projets

On a utilisé NextAuth sur quatre projets en production entre 2022 et 2024. Sur trois d'entre eux, on a fini par le retirer. Ce n'est pas une décision idéologique, c'est le résultat d'incidents répétés, de sessions de debug longues, et d'une incompatibilité croissante avec la direction que prend Next.js.

Ce que NextAuth fait bien

Soyons honnêtes. NextAuth (devenu Auth.js) couvre une surface réelle : intégration OAuth avec des dizaines de providers, gestion des sessions JWT ou base de données, callbacks de transformation, adapters pour Prisma, Drizzle, MongoDB.

Pour un projet avec authentification sociale simple (Google, GitHub) et une base de données, il déroule en quelques heures. La documentation est correcte, la communauté est grande.

Le problème n'est pas qu'il soit mauvais. Le problème est ce qu'il cache et ce qu'il impose.

Les frictions qu'on a rencontrées concrètement

Le modèle de session opaque

NextAuth gère la session dans un cookie httpOnly signé. Pour y accéder côté serveur dans un Server Component, on appelle auth() ou getServerSession(). Simple en apparence.

Mais dès qu'on veut accéder à des claims personnalisés, un organizationId, un role, un plan, on entre dans les callbacks jwt et session. Ces deux callbacks s'exécutent à des moments différents, dans des ordres qui ne sont pas toujours évidents, et leur interaction avec les Server Actions et le middleware crée des incohérences.

On a eu un bug de production où le role mis à jour en base n'était pas reflété dans la session avant que l'utilisateur se reconnecte. La correction a demandé deux jours de debug, pas parce que la solution était complexe, mais parce que le flux d'exécution des callbacks n'était pas documenté pour ce cas précis.

La migration v3 → v4 (NextAuth → Auth.js)

Le renommage et la refonte de l'API entre NextAuth v3 et Auth.js v4 ont cassé plusieurs de nos projets. Les adapters ont changé d'interface, le schéma de base de données a évolué, les imports ont changé. Sur un projet avec sessions en base, la migration a nécessité une migration de schéma et deux jours de tests.

Ce n'est pas critique pour un projet en phase de développement. En production, avec des utilisateurs actifs, c'est une fenêtre de risque inacceptable.

L'incompatibilité avec l'App Router

Auth.js v5 (beta) supporte mieux l'App Router, mais on est restés longtemps sur des patterns qui mélangent pages/api/auth/[...nextauth] avec des routes App Router. Le middleware d'Auth.js qui lit la session a des comportements différents selon qu'on est dans le Edge Runtime ou le Node.js Runtime. On a passé du temps sur des erreurs d'exécution qui venaient de cette frontière.

Ce qu'on utilise à la place

Le choix dépend du contexte du projet. On n'a pas de solution unique.

Better Auth, notre choix par défaut aujourd'hui

Better Auth est plus récent (fin 2024), mais son modèle est plus clair. Il sépare explicitement la couche session, la couche utilisateur, et les providers. L'API est cohérente, le typage TypeScript est correct dès le départ, et son support de l'App Router est natif.

// Configuration
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg" }),
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    updateAge: 60 * 60 * 24,
  },
})

Ce qui nous a convaincus : les plugins. La gestion du multi-tenant, des organisations, des rôles, des API keys, tout est modulaire et bien typé. On n'écrit pas des callbacks qui s'enchaînent de manière implicite ; on active des fonctionnalités avec une configuration explicite.

Lucia Auth, pour les projets avec contrôle maximal

Lucia est une bibliothèque bas niveau. Elle ne gère pas les providers OAuth directement : elle gère les sessions et les utilisateurs, et laisse le reste au développeur.

C'est plus de code à écrire. C'est aussi un code qu'on comprend entièrement, qu'on peut auditer, et qui ne cache pas de logique dans des callbacks.

Pour des projets avec des exigences de sécurité élevées, authentification interne, gestion de tokens personnalisés, intégration avec un système d'identité existant, Lucia est ce qu'on utilise. On contrôle la rotation des sessions, la durée de vie des tokens, la structure des claims.

Clerk, pour les projets SaaS où l'auth n'est pas un différenciateur

Clerk est un service externe. C'est une décision de délégation : on ne gère plus l'auth, on l'achète.

On l'utilise sur des projets SaaS B2B où la priorité est d'aller vite, où les clients attendent du SSO SAML, de la gestion d'organisations, du MFA, des fonctionnalités qui prendraient plusieurs semaines à implémenter correctement. Clerk les fournit prêtes à l'emploi.

Le prix en est la dépendance à un tiers et le coût mensuel (qui devient significatif à partir de 10 000 utilisateurs actifs mensuels).

La question de sécurité qu'on pose sur chaque projet

Avant de choisir une solution d'auth, on pose trois questions.

Qui gère la rotation des sessions en cas de compromission ? Avec NextAuth en mode JWT, révoquer toutes les sessions actives n'est pas trivial. Avec Better Auth ou Lucia en mode base de données, c'est une DELETE FROM sessions.

Les tokens de session sont-ils opaques ou contiennent-ils des claims ? Un JWT signé mais non chiffré expose ses claims à quiconque le décode (base64). Si on met un role ou un userId dans un JWT, il faut savoir que ces données sont lisibles côté client.

Quelle est la surface d'attaque du middleware ? Le middleware Next.js s'exécute sur chaque requête. Une erreur dans la lecture de session à ce niveau peut bloquer tous les utilisateurs. On teste le middleware en isolation et on maintient un circuit breaker : si la session est illisible, on redirige vers le login plutôt que de crasher.

Ce que NextAuth fait encore bien

Pour un projet personnel, un prototype rapide avec OAuth social, ou une équipe qui connaît déjà Auth.js v5 : ce n'est pas un mauvais choix. La v5 a corrigé une partie des problèmes qu'on a décrits.

Notre critique porte sur l'utilisation en production avec des exigences métier complexes : multi-tenant, rôles granulaires, contrôle de la rétention des sessions, audit des connexions. Sur ces cas, les abstractions de NextAuth deviennent des obstacles.

La question reste ouverte : est-ce que Better Auth tiendra la même promesse dans deux ans, quand les projets qui l'utilisent aujourd'hui devront migrer vers une version majeure ?

Disponible pour de nouveaux projets

Un projet à concrétiser ?
Parlons-en, sans engagement.

Un échange de 30 minutes pour cadrer votre besoin, qualifier la faisabilité et vous proposer une trajectoire claire.

1// kick-off : réponse sous 24h
2const project = await apogee.scope({
3 type: 'web | mobile | IA',
4 timeline: '6 à 16 semaines',
5 approach: 'sur-mesure'
6})
7// → cadrage offert
Alternative NextAuth: pourquoi on a changé d'auth library | Apogée Consult