Gestion de session et cookies sécurisés

Implémente la gestion de session, cookies httpOnly et redirections.

4 min read

Accéder gratuitement à cette formation

Renseigne ton email pour débloquer immédiatement cette formation gratuite.

Bienvenue dans cette leçon dédiée à la persistance de l’authentification grâce aux cookies sécurisés sous Next.js 15. Tu vas découvrir comment :

  • Générer et lire un cookie httpOnly et secure.
  • Extraire l’ID de session pour authentifier tes pages.
  • Protéger les routes critiques (requireUser).
  • Implémenter un logout fiable.

Pourquoi utiliser des cookies sécurisés ?

La session en cookie permet de :

  1. Stocker un identifiant sans l’exposer côté client (httpOnly).
  2. Limiter la portée du cookie (path, maxAge).
  3. Activer le flag secure pour HTTPS en production.

Ces bonnes pratiques réduisent les risques de vol de session et d’attaque XSS.


API cookies de Next.js

Next.js 15 expose un helper cookies() utilisable exclusivement dans un contexte serveur ('use server').
Tu peux :

  • set pour stocker un cookie.
  • get pour le récupérer.
  • delete pour effacer la session.

Implémenter session.ts

app/server/session.ts
1
'use server'
2
import { cookies } from 'next/headers'
3
import { prisma } from './db'
4
5
const sessionCookieName = 'user_session'
6
7
export async function setUserId({ userId }: { userId: string }) {
8
const cookieStore = await cookies()
9
cookieStore.set(sessionCookieName, userId, {
10
secure: process.env.NODE_ENV === 'production', // @callout: actif en prod seulement
11
httpOnly: true, // @callout: interdit l’accès en JS
12
path: '/', // ^? accessible sur tout le domaine
13
maxAge: 60 * 60 * 24 * 30, // ^? durée de vie : 30 jours
14
})
15
}
16
17
export async function getUserId() {
18
const cookieStore = await cookies()
19
return cookieStore.get(sessionCookieName)?.value ?? null
20
}
21
22
export async function logout() {
23
const cookieStore = await cookies()
24
cookieStore.delete(sessionCookieName)
25
}
26
27
export async function getOptionalUser() {
28
const userId = await getUserId()
29
if (!userId) return null
30
return prisma.user.findUnique({
31
where: { id: Number(userId) },
32
select: { email: true },
33
})
34
}
35
36
export async function requireUser() {
37
const user = await getOptionalUser()
38
if (!user) throw new Error('Utilisateur non authentifié')
39
return user
40
}

Intégration dans l’authentification

Dans ton server/auth.ts, après avoir validé l’utilisateur, il suffit d’appeler setUserId pour déclencher la création du cookie, puis redirect('/') pour renvoyer la home page.

app/server/auth.ts
1
'use server'
2
import { redirect } from 'next/navigation'
3
import { setUserId } from './session'
4
import { compare } from 'bcryptjs'
5
import { prisma } from './db'
6
7
export async function login(
8
prevState: unknown,
9
formData: FormData
10
) {
11
'use server'
12
const email = formData.get('email') as string
13
const password = formData.get('password') as string
14
15
const user = await prisma.user.findUnique({ where: { email } })
16
if (!user) throw new Error('Utilisateur non trouvé')
17
18
const valid = await compare(password, user.password)
19
if (!valid) throw new Error('Mot de passe invalide')
20
21
await setUserId({ userId: user.id.toString() })
22
redirect('/')
23
}

Protéger une page avec requireUser

Pour sécuriser l’accès à /history, utilise requireUser() en début de ta Server Component :

app/history/page.tsx
1
'use server'
2
import { requireUser } from '../server/session'
3
import { HistoryList } from './HistoryList'
4
import { getHistory } from '../server/history'
5
6
export default async function HistoryPage() {
7
const user = await requireUser()
8
const items = await getHistory()
9
return <HistoryList user={user} initialHistoryItems={items} />
10
}

Ajouter le bouton de logout dans le layout

Dans app/layout.tsx, fais appel à getOptionalUser() pour afficher l’email et un formulaire Logout.
Le form action peut être une lambda 'use server' qui appelle logout().

app/layout.tsx
1
...
2
export default async function RootLayout({ children }: { children: ReactNode }) {
3
const user = await getOptionalUser()
4
return (
5
<html lang="fr">
6
<body>
7
<nav>
8
{user ? (
9
<>
10
<span>{user.email}</span>
11
<form action={async () => {
12
'use server'
13
await logout()
14
}}>
15
<button type="submit">Logout</button>
16
</form>
17
</>
18
) : (
19
<>
20
<Link href="/login">Login</Link>
21
<Link href="/register">Register</Link>
22
</>
23
)}
24
</nav>
25
{children}
26
</body>
27
</html>
28
)
29
}

Récapitulatif des points clés

  • cookies() : set, get, delete.
  • httpOnly + secure renforcent la sécurité.
  • Mettre path: '/' et maxAge approprié.
  • Utiliser getOptionalUser() pour récupérer le user ou null.
  • requireUser() pour forcer l’accès authentifié.
  • Intégration avec Server Actions + redirect de Next.js 15.

Exercices rapides

  1. Configurer un cookie
    Dans app/server/session.ts, modifie maxAge pour 7 jours au lieu de 30 et teste la suppression automatique.

  2. Protéger une API route
    Crée app/api/profile/route.ts qui retourne les infos de l’utilisateur connecté ou 401 si non authentifié (utilise requireUser()).

  3. Test de logout
    Ajoute un message flash après déconnection :

    • Stocke un cookie temporaire flash="À bientôt !"
    • Lis-le côté client dans le layout pour afficher la notification, puis supprime-le.

Bonne session !