Comment implémenter l'authentification dans Next.js
Comprendre l'authentification est crucial pour protéger les données de votre application. Cette page vous guidera sur les fonctionnalités de React et Next.js à utiliser pour implémenter l'authentification.
Avant de commencer, il est utile de décomposer le processus en trois concepts :
- Authentification : Vérifie si l'utilisateur est bien celui qu'il prétend être. Cela nécessite que l'utilisateur prouve son identité avec quelque chose qu'il connaît, comme un nom d'utilisateur et un mot de passe.
- Gestion de session : Suit l'état d'authentification de l'utilisateur entre les requêtes.
- Autorisation : Détermine quelles routes et données l'utilisateur peut accéder.
Ce diagramme montre le flux d'authentification utilisant les fonctionnalités de React et Next.js :

Les exemples de cette page présentent une authentification basique avec nom d'utilisateur et mot de passe à des fins éducatives. Bien que vous puissiez implémenter une solution d'authentification personnalisée, pour une meilleure sécurité et simplicité, nous recommandons d'utiliser une bibliothèque d'authentification. Celles-ci offrent des solutions intégrées pour l'authentification, la gestion de session et l'autorisation, ainsi que des fonctionnalités supplémentaires comme les connexions sociales, l'authentification multi-facteurs et le contrôle d'accès basé sur les rôles. Vous trouverez une liste dans la section Bibliothèques d'authentification.
Authentification
Voici les étapes pour implémenter un formulaire d'inscription et/ou de connexion :
- L'utilisateur soumet ses identifiants via un formulaire.
- Le formulaire envoie une requête traitée par une route API.
- Après vérification réussie, le processus se termine, indiquant l'authentification réussie.
- En cas d'échec, un message d'erreur s'affiche.
Prenons un formulaire de connexion où les utilisateurs peuvent saisir leurs identifiants :
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// Gérer les erreurs
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Mot de passe" required />
<button type="submit">Se connecter</button>
</form>
)
}
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// Gérer les erreurs
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Mot de passe" required />
<button type="submit">Se connecter</button>
</form>
)
}
Le formulaire ci-dessus a deux champs pour capturer l'email et le mot de passe. À la soumission, il déclenche une fonction qui envoie une requête POST à une route API (/api/auth/login
).
Vous pouvez ensuite appeler l'API de votre fournisseur d'authentification dans la route API pour gérer l'authentification :
import type { NextApiRequest, NextApiResponse } from 'next'
import { signIn } from '@/auth'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'Identifiants invalides.' })
} else {
res.status(500).json({ error: 'Une erreur est survenue.' })
}
}
}
import { signIn } from '@/auth'
export default async function handler(req, res) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'Identifiants invalides.' })
} else {
res.status(500).json({ error: 'Une erreur est survenue.' })
}
}
}
Gestion des sessions
La gestion des sessions garantit que l'état authentifié de l'utilisateur est conservé entre les requêtes. Elle implique la création, le stockage, le rafraîchissement et la suppression des sessions ou jetons.
Il existe deux types de sessions :
- Sans état (Stateless) : Les données de session (ou un jeton) sont stockées dans les cookies du navigateur. Le cookie est envoyé avec chaque requête, permettant de vérifier la session côté serveur. Cette méthode est plus simple mais moins sécurisée si mal implémentée.
- Base de données (Database) : Les données de session sont stockées dans une base de données, le navigateur ne recevant que l'ID de session chiffré. Cette méthode est plus sécurisée mais plus complexe et consomme plus de ressources serveur.
Bon à savoir : Bien que vous puissiez utiliser les deux méthodes, nous recommandons d'utiliser une bibliothèque de gestion de session comme iron-session ou Jose.
Sessions sans état (Stateless)
Définition et suppression des cookies
Vous pouvez utiliser les Routes API pour définir la session comme un cookie sur le serveur :
import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // Une semaine
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'Cookie défini avec succès !' })
}
import { serialize } from 'cookie'
import { encrypt } from '@/app/lib/session'
export default function handler(req, res) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // Une semaine
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'Cookie défini avec succès !' })
}
Sessions de base de données
Pour créer et gérer des sessions de base de données, vous devrez suivre ces étapes :
- Créez une table dans votre base de données pour stocker les sessions et les données (ou vérifiez si votre bibliothèque d'authentification s'en charge).
- Implémentez des fonctionnalités pour insérer, mettre à jour et supprimer des sessions.
- Chiffrez l'ID de session avant de le stocker dans le navigateur de l'utilisateur, et assurez-vous que la base de données et le cookie restent synchronisés (ceci est facultatif, mais recommandé pour les vérifications optimistes d'authentification dans le Middleware).
Création d'une session sur le serveur :
import db from '../../lib/db'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'Erreur interne du serveur' })
}
}
import db from '../../lib/db'
export default async function handler(req, res) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'Erreur interne du serveur' })
}
}
Autorisation
Une fois qu'un utilisateur est authentifié et qu'une session est créée, vous pouvez implémenter l'autorisation pour contrôler ce que l'utilisateur peut accéder et faire dans votre application.
Il existe deux principaux types de vérifications d'autorisation :
- Optimiste : Vérifie si l'utilisateur est autorisé à accéder à une route ou à effectuer une action en utilisant les données de session stockées dans le cookie. Ces vérifications sont utiles pour des opérations rapides, comme afficher/masquer des éléments d'interface ou rediriger les utilisateurs en fonction des permissions ou rôles.
- Sécurisée : Vérifie si l'utilisateur est autorisé à accéder à une route ou à effectuer une action en utilisant les données de session stockées dans la base de données. Ces vérifications sont plus sécurisées et sont utilisées pour des opérations nécessitant un accès à des données sensibles ou des actions.
Pour les deux cas, nous recommandons :
- De créer une Couche d'Accès aux Données (CAD) pour centraliser votre logique d'autorisation.
- D'utiliser des Objets de Transfert de Données (OTD) pour ne retourner que les données nécessaires.
- D'utiliser optionnellement le Middleware pour effectuer des vérifications optimistes.
Vérifications optimistes avec le Middleware (Facultatif)
Il existe certains cas où vous pouvez vouloir utiliser le Middleware et rediriger les utilisateurs en fonction des permissions :
- Pour effectuer des vérifications optimistes. Comme le Middleware s'exécute sur chaque route, c'est un bon moyen de centraliser la logique de redirection et de pré-filtrer les utilisateurs non autorisés.
- Pour protéger des routes statiques qui partagent des données entre utilisateurs (par exemple, du contenu derrière un paywall).
Cependant, comme le Middleware s'exécute sur chaque route, y compris les routes préchargées, il est important de ne lire la session que depuis le cookie (vérifications optimistes), et d'éviter les vérifications en base de données pour prévenir les problèmes de performance.
Par exemple :
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. Spécifiez les routes protégées et publiques
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req: NextRequest) {
// 2. Vérifiez si la route actuelle est protégée ou publique
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. Déchiffrez la session depuis le cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 4. Redirigez vers /login si l'utilisateur n'est pas authentifié
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 5. Redirigez vers /dashboard si l'utilisateur est authentifié
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Routes sur lesquelles le Middleware ne doit pas s'exécuter
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. Spécifiez les routes protégées et publiques
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req) {
// 2. Vérifiez si la route actuelle est protégée ou publique
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. Déchiffrez la session depuis le cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 5. Redirigez vers /login si l'utilisateur n'est pas authentifié
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 6. Redirigez vers /dashboard si l'utilisateur est authentifié
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Routes sur lesquelles le Middleware ne doit pas s'exécuter
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
Bien que le Middleware puisse être utile pour des vérifications initiales, il ne devrait pas être votre seule ligne de défense pour protéger vos données. La majorité des vérifications de sécurité devraient être effectuées aussi près que possible de votre source de données, voir Couche d'Accès aux Données pour plus d'informations.
Conseils :
- Dans le Middleware, vous pouvez également lire les cookies en utilisant
req.cookies.get('session').value
.- Le Middleware utilise le Edge Runtime, vérifiez si votre bibliothèque d'authentification et votre bibliothèque de gestion de session sont compatibles.
- Vous pouvez utiliser la propriété
matcher
dans le Middleware pour spécifier sur quelles routes le Middleware doit s'exécuter. Cependant, pour l'authentification, il est recommandé que le Middleware s'exécute sur toutes les routes.
Création d'une couche d'accès aux données (DAL)
Protection des routes API
Les routes API dans Next.js sont essentielles pour gérer la logique côté serveur et la gestion des données. Il est crucial de sécuriser ces routes pour s'assurer que seuls les utilisateurs autorisés peuvent accéder à des fonctionnalités spécifiques. Cela implique généralement de vérifier le statut d'authentification de l'utilisateur et ses permissions basées sur son rôle.
Voici un exemple de sécurisation d'une route API :
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getSession(req)
// Vérifier si l'utilisateur est authentifié
if (!session) {
res.status(401).json({
error: 'L\'utilisateur n\'est pas authentifié',
})
return
}
// Vérifier si l'utilisateur a le rôle 'admin'
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Accès non autorisé : L\'utilisateur n\'a pas les privilèges d\'admin.',
})
return
}
// Poursuivre la route pour les utilisateurs autorisés
// ... implémentation de la route API
}
export default async function handler(req, res) {
const session = await getSession(req)
// Vérifier si l'utilisateur est authentifié
if (!session) {
res.status(401).json({
error: 'L\'utilisateur n\'est pas authentifié',
})
return
}
// Vérifier si l'utilisateur a le rôle 'admin'
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Accès non autorisé : L\'utilisateur n\'a pas les privilèges d\'admin.',
})
return
}
// Poursuivre la route pour les utilisateurs autorisés
// ... implémentation de la route API
}
Cet exemple démontre une route API avec une vérification de sécurité à deux niveaux pour l'authentification et l'autorisation. Elle vérifie d'abord une session active, puis confirme si l'utilisateur connecté est un 'admin'. Cette approche garantit un accès sécurisé, limité aux utilisateurs authentifiés et autorisés, maintenant une sécurité robuste pour le traitement des requêtes.
Ressources
Maintenant que vous avez appris l'authentification dans Next.js, voici des bibliothèques compatibles avec Next.js et des ressources pour vous aider à implémenter une authentification et une gestion de session sécurisées :
Bibliothèques d'authentification
Bibliothèques de gestion de session
Pour aller plus loin
Pour continuer à apprendre sur l'authentification et la sécurité, consultez les ressources suivantes :