Cours Next.js · Module 7: Authentification et autorisation
Mise en œuvre de NextAuth.js / Auth.js
Dans cette leçon14
NextAuth.js (désormais aussi appelé Auth.js) est une solution d'authentification complète pour les applications Next.js. Elle fournit une infrastructure prête à l'emploi pour gérer les sessions, les jetons, la connexion, l'inscription et l'intégration avec des fournisseurs d'authentification externes. Dans ce module, nous allons voir en détail la mise en œuvre de cet outil dans les applications Next.js.
Pourquoi utiliser NextAuth.js ?
NextAuth.js offre de nombreux avantages :
- Solution prête à l'emploi - pas besoin d'implémenter l'authentification de zéro
- Prise en charge de divers fournisseurs - intégration avec plus de 50 fournisseurs d'authentification
- Sécurité - applique les bonnes pratiques de sécurité
- Gestion des sessions - gère les sessions côté serveur comme côté client
- Flexibilité - peut être adapté aux besoins spécifiques de l'application
- Prise en charge de l'App Router - fonctionne avec la nouvelle architecture de Next.js
- Typage TypeScript étendu - offre un support complet des types
Installation et configuration de base
Commençons par installer NextAuth.js :
1npm install next-auth@latest
2# ou
3yarn add next-auth@latest
4# ou
5pnpm add next-auth@latestConfiguration de base pour l'App Router
Pour configurer NextAuth.js dans l'architecture App Router, nous devons créer un fichier qui gère la route API d'authentification :
1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from "next-auth";
3import GithubProvider from "next-auth/providers/github";
4
5export const authOptions = {
6 providers: [
7 GithubProvider({
8 clientId: process.env.GITHUB_ID!,
9 clientSecret: process.env.GITHUB_SECRET!
10 })
11 ],
12 // Options de configuration supplémentaires...
13};
14
15const handler = NextAuth(authOptions);
16export { handler as GET, handler as POST };Ce fichier crée une route API dynamique qui gère tous les points d'authentification nécessaires.
Ajouter le composant SessionProvider
Pour pouvoir utiliser le hook useSession dans les composants côté client, nous devons ajouter SessionProvider à notre application. Le mieux est de le faire dans un fichier Provider ou directement dans le layout principal :
1// app/providers.tsx
2'use client';
3
4import { SessionProvider } from "next-auth/react";
5import { ReactNode } from "react";
6
7export function Providers({ children }: { children: ReactNode }) {
8 return <SessionProvider>{children}</SessionProvider>;
9}
10
11// app/layout.tsx
12import { Providers } from "./providers";
13
14export default function RootLayout({
15 children,
16}: {
17 children: React.ReactNode;
18}) {
19 return (
20 <html lang="en">
21 <body>
22 <Providers>{children}</Providers>
23 </body>
24 </html>
25 );
26}Configuration des variables d'environnement
NextAuth.js nécessite plusieurs variables d'environnement. Crée ou mets à jour le fichier .env.local à la racine du projet :
1# Configuration de base pour NextAuth.js
2NEXTAUTH_URL=http://localhost:3000
3NEXTAUTH_SECRET=ta_cle_secrete_pour_le_chiffrement_jwt
4
5# Données d'authentification GitHub (exemple)
6GITHUB_ID=ton_github_client_id
7GITHUB_SECRET=ton_github_client_secretEn production :
NEXTAUTH_URLdoit être l'URL complète de ton applicationNEXTAUTH_SECRETdoit être une longue chaîne de caractères aléatoires. Tu peux la générer avec la commandeopenssl rand -base64 32.
Configuration de différents fournisseurs d'authentification
NextAuth.js prend en charge de nombreux fournisseurs d'authentification. Voyons comment en configurer quelques-uns parmi les plus populaires :
1. Authentification avec GitHub
1import GithubProvider from "next-auth/providers/github";
2
3export const authOptions = {
4 providers: [
5 GithubProvider({
6 clientId: process.env.GITHUB_ID!,
7 clientSecret: process.env.GITHUB_SECRET!,
8 // Opcjonalne parametry
9 scope: "read:user user:email", // Specifies access scope
10 profile(profile) {
11 // Customizing user profile
12 return {
13 id: profile.id.toString(),
14 name: profile.name || profile.login,
15 email: profile.email,
16 image: profile.avatar_url,
17 // Dodatkowe pola
18 username: profile.login,
19 };
20 },
21 }),
22 ],
23 // Other configuration options...
24};2. Authentification avec Google
1import GoogleProvider from "next-auth/providers/google";
2
3export const authOptions = {
4 providers: [
5 GoogleProvider({
6 clientId: process.env.GOOGLE_CLIENT_ID!,
7 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
8 // Opcjonalne parametry
9 authorization: {
10 params: Promise<{
11 prompt: "consent",
12 access_type: "offline",
13 response_type: "code"
14 }>
15 }
16 }),
17 ],
18 // Other configuration options...
19};3. Authentification par identifiants (e-mail/mot de passe)
1import CredentialsProvider from "next-auth/providers/credentials";
2import { compare } from "bcrypt";
3import { prisma } from "@/lib/prisma"; // We assume you have a configured Prisma instance
4
5export const authOptions = {
6 providers: [
7 CredentialsProvider({
8 // Name displayed on the login page
9 name: "Credentials",
10 // Login form field configuration
11 credentials: {
12 email: { label: "Email", type: "email" },
13 password: { label: "Password", type: "password" }
14 },
15 async authorize(credentials) {
16 // Check if credentials exist
17 if (!credentials?.email || !credentials?.password) {
18 return null;
19 }
20
21 try {
22 // Find the user in the database
23 const user = await prisma.user.findUnique({
24 where: { email: credentials.email },
25 });
26
27 if (!user || !user.password) {
28 return null;
29 }
30
31 // Check if the password is correct
32 const isPasswordValid = await compare(
33 credentials.password,
34 user.password
35 );
36
37 if (!isPasswordValid) {
38 return null;
39 }
40
41 // Return the user object (without password)
42 return {
43 id: user.id,
44 name: user.name,
45 email: user.email,
46 role: user.role,
47 // Other fields you want to include
48 };
49 } catch (error) {
50 console.error("Authentication error:", error);
51 return null;
52 }
53 },
54 }),
55 ],
56 // Other configuration options...
57};4. Authentification depuis plusieurs fournisseurs
Tu peux combiner plusieurs fournisseurs d'authentification :
1export const authOptions = {
2 providers: [
3 GithubProvider({
4 clientId: process.env.GITHUB_ID!,
5 clientSecret: process.env.GITHUB_SECRET!,
6 }),
7 GoogleProvider({
8 clientId: process.env.GOOGLE_CLIENT_ID!,
9 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
10 }),
11 CredentialsProvider({
12 // Configuration...
13 async authorize(credentials) {
14 // Logique d'authentification...
15 },
16 }),
17 // Tu peux ajouter d'autres fournisseurs...
18 ],
19 // Autres options de configuration...
20};Configuration des options de session et JWT
NextAuth.js propose différentes stratégies de session :
1export const authOptions = {
2 // Provider configuration...
3
4 session: {
5 // Choose strategy: 'jwt' or 'database'
6 strategy: 'jwt',
7
8 // Maximum session lifetime (in seconds)
9 maxAge: 30 * 24 * 60 * 60, // 30 dni
10
11 // Session update on each request, extending its validity
12 updateAge: 24 * 60 * 60, // 24 godziny
13 },
14
15 // Configuration JWT (uniquement pour la stratégie 'jwt')
16 jwt: {
17 // Secret key used to sign JWT tokens
18 // By default uses NEXTAUTH_SECRET
19 secret: process.env.JWT_SECRET,
20
21 // You can customize JWT encoder/decoder
22 // encode: async ({ secret, token, maxAge }) => {},
23 // decode: async ({ secret, token }) => {},
24 },
25};Pages d'authentification personnalisées
Par défaut, NextAuth.js fournit des pages de connexion simples, mais tu peux les remplacer par les tiennes :
1export const authOptions = {
2 // Provider configuration...
3
4 pages: {
5 signIn: '/auth/login',
6 signOut: '/auth/logout',
7 error: '/auth/error', // Authentication error (e.g. invalid credentials)
8 verifyRequest: '/auth/verify-request', // Utilisé pour les Magic Links
9 newUser: '/auth/new-user' // Page affichée après la première inscription
10 },
11};Il faut ensuite créer ces pages dans ton application. Voici un exemple de page de connexion :
1// app/auth/login/page.tsx
2'use client';
3
4import { signIn, useSession } from 'next-auth/react';
5import { useRouter, useSearchParams } from 'next/navigation';
6import { useState, useEffect } from 'react';
7
8export default function LoginPage() {
9 const router = useRouter();
10 const searchParams = useSearchParams();
11 const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
12 const { status } = useSession();
13
14 const [email, setEmail] = useState('');
15 const [password, setPassword] = useState('');
16 const [error, setError] = useState('');
17 const [isLoading, setIsLoading] = useState(false);
18
19 // Redirect the logged-in user
20 useEffect(() => {
21 if (status === 'authenticated') {
22 router.push(callbackUrl);
23 }
24 }, [status, router, callbackUrl]);
25
26 // GitHub login handling
27 const handleGitHubSignIn = () => {
28 setIsLoading(true);
29 signIn('github', { callbackUrl });
30 };
31
32 // Google login handling
33 const handleGoogleSignIn = () => {
34 setIsLoading(true);
35 signIn('google', { callbackUrl });
36 };
37
38 // Credentials login handling
39 const handleSubmit = async (e: React.FormEvent) => {
40 e.preventDefault();
41 setIsLoading(true);
42 setError('');
43
44 try {
45 const result = await signIn('credentials', {
46 redirect: false,
47 email,
48 password,
49 });
50
51 if (result?.error) {
52 setError('E-mail ou mot de passe invalide');
53 setIsLoading(false);
54 } else {
55 router.push(callbackUrl);
56 }
57 } catch (error) {
58 setError('Une erreur est survenue lors de la connexion');
59 setIsLoading(false);
60 }
61 };
62
63 if (status === 'loading') {
64 return <div>Chargement...</div>;
65 }
66
67 return (
68 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
69 <h1 className="text-2xl font-bold mb-6 text-center">Se connecter</h1>
70
71 {error && (
72 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
73 {error}
74 </div>
75 )}
76
77 <div className="space-y-4 mb-6">
78 <button
79 type="button"
80 onClick={handleGitHubSignIn}
81 disabled={isLoading}
82 className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
83 >
84 <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
85 {/* Logo GitHub */}
86 <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
87 </svg>
88 Continuer avec GitHub
89 </button>
90
91 <button
92 type="button"
93 onClick={handleGoogleSignIn}
94 disabled={isLoading}
95 className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
96 >
97 <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
98 {/* Logo Google */}
99 <path d="M12 24C5.385 24 0 18.615 0 12S5.385 0 12 0s12 5.385 12 12-5.385 12-12 12zm0-22.5C6.21 1.5 1.5 6.21 1.5 12S6.21 22.5 12 22.5 22.5 17.79 22.5 12 17.79 1.5 12 1.5zm-5.25 9v3h3v3h3v-3h3v-3h-3v-3h-3v3h-3z" />
100 </svg>
101 Continuer avec Google
102 </button>
103 </div>
104
105 <div className="relative my-6">
106 <div className="absolute inset-0 flex items-center">
107 <div className="w-full border-t border-gray-300"></div>
108 </div>
109 <div className="relative flex justify-center text-sm">
110 <span className="px-2 bg-white text-gray-500">
111 ou connecte-toi avec ton e-mail
112 </span>
113 </div>
114 </div>
115
116 <form onSubmit={handleSubmit} className="space-y-4">
117 <div>
118 <label htmlFor="email" className="block text-sm font-medium mb-1">
119 Email
120 </label>
121 <input
122 id="email"
123 type="email"
124 value={email}
125 onChange={(e) => setEmail(e.target.value)}
126 required
127 className="w-full px-3 py-2 border border-gray-300 rounded-md"
128 />
129 </div>
130
131 <div>
132 <label htmlFor="password" className="block text-sm font-medium mb-1">
133 Mot de passe
134 </label>
135 <input
136 id="password"
137 type="password"
138 value={password}
139 onChange={(e) => setPassword(e.target.value)}
140 required
141 className="w-full px-3 py-2 border border-gray-300 rounded-md"
142 />
143 </div>
144
145 <button
146 type="submit"
147 disabled={isLoading}
148 className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 disabled:opacity-50"
149 >
150 {isLoading ? 'Connexion en cours...' : 'Se connecter'}
151 </button>
152 </form>
153 </div>
154 );
155}Callbacks et événements
NextAuth.js permet de réagir à différents événements du cycle de vie de l'authentification grâce aux callbacks :
1export const authOptions = {
2 // Provider configuration...
3
4 callbacks: {
5 // Called when creating a session
6 async session({ session, token }) {
7 // Ajoute les données du jeton à la session
8 if (token.sub && session.user) {
9 session.user.id = token.sub;
10 }
11
12 // Add custom fields, e.g. user role
13 if (token.role && session.user) {
14 session.user.role = token.role as string;
15 }
16
17 return session;
18 },
19
20 // Called when creating a JWT
21 async jwt({ token, user, account, profile }) {
22 // Add user data on first login
23 if (user) {
24 token.role = user.role;
25 // Autres champs personnalisés...
26 }
27
28 return token;
29 },
30
31 // Called after login
32 async signIn({ user, account, profile, email, credentials }) {
33 // Check if the user has access to the application
34 return true; // Return false to deny access
35 },
36
37 // Called during redirect
38 async redirect({ url, baseUrl }) {
39 // Redirect rules
40 if (url.startsWith(baseUrl)) {
41 return url;
42 }
43 // By default redirect to the base page
44 return baseUrl;
45 },
46 },
47
48 // Événements
49 events: {
50 async signIn(message) {
51 // Called after login
52 console.log('User signed in:', message);
53 },
54 async signOut(message) {
55 // Called after logout
56 console.log('User signed out:', message);
57 },
58 async createUser(message) {
59 // Called after user creation
60 console.log('User created:', message);
61 },
62 async linkAccount(message) {
63 // Called after account linking
64 console.log('Account linked:', message);
65 },
66 async session(message) {
67 // Called after session creation
68 console.log('Session created/updated:', message);
69 },
70 },
71};Étendre les types pour TypeScript
Si tu utilises TypeScript, tu peux étendre les types de NextAuth.js pour y inclure des champs personnalisés :
1// In the type extension file (e.g. types/next-auth.d.ts)
2import 'next-auth';
3import 'next-auth/jwt';
4
5declare module 'next-auth' {
6 interface Session {
7 user: {
8 id: string;
9 name?: string | null;
10 email?: string | null;
11 image?: string | null;
12 role?: string;
13 // Ajoute d'autres champs personnalisés...
14 };
15 }
16
17 interface User {
18 id: string;
19 name?: string | null;
20 email?: string | null;
21 image?: string | null;
22 role?: string;
23 // Ajouter d'autres champs personnalisés...
24 }
25}
26
27declare module 'next-auth/jwt' {
28 interface JWT {
29 role?: string;
30 // Ajouter d'autres champs personnalisés...
31 }
32}Adaptateurs de base de données
Pour utiliser la stratégie de session basée sur la base de données ou pour stocker les comptes utilisateurs, NextAuth.js propose des adaptateurs pour différentes bases de données :
Configuration avec Prisma
1import { PrismaAdapter } from "@auth/prisma-adapter";
2import { PrismaClient } from "@prisma/client";
3
4const prisma = new PrismaClient();
5
6export const authOptions = {
7 adapter: PrismaAdapter(prisma),
8 providers: [
9 // Provider configuration...
10 ],
11 // Other options...
12};Le schéma Prisma doit contenir les modèles appropriés :
1// prisma/schema.prisma
2model Account {
3 id String @id @default(cuid())
4 userId String
5 type String
6 provider String
7 providerAccountId String
8 refresh_token String? @db.Text
9 access_token String? @db.Text
10 expires_at Int?
11 token_type String?
12 scope String?
13 id_token String? @db.Text
14 session_state String?
15
16 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
17
18 @@unique([provider, providerAccountId])
19}
20
21model Session {
22 id String @id @default(cuid())
23 sessionToken String @unique
24 userId String
25 expires DateTime
26 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
27}
28
29model User {
30 id String @id @default(cuid())
31 name String?
32 email String? @unique
33 emailVerified DateTime?
34 image String?
35 password String?
36 role String? @default("user")
37 accounts Account[]
38 sessions Session[]
39}
40
41model VerificationToken {
42 identifier String
43 token String @unique
44 expires DateTime
45
46 @@unique([identifier, token])
47}Autres adaptateurs disponibles
NextAuth.js propose des adaptateurs pour de nombreuses bases de données :
- Adaptateurs SQL : PostgreSQL, MySQL, SQLite, SQL Server
- Adaptateurs non relationnels : MongoDB, DynamoDB
- ORM/ODM : Prisma, Mongoose, Typeorm, Sequelize, DrizzleORM
- Autres : Firebase, Supabase, Kysely, Planetscale, Xata
Utiliser l'authentification dans l'application
1. Récupérer les données de session côté client
Après avoir configuré NextAuth.js, tu peux utiliser le hook useSession dans les composants client :
1'use client';
2
3import { useSession } from 'next-auth/react';
4import { redirect } from 'next/navigation';
5
6export default function Dashboard() {
7 const { data: session, status } = useSession({
8 required: true,
9 onUnauthenticated() {
10 redirect('/auth/login?callbackUrl=/dashboard');
11 },
12 });
13
14 if (status === 'loading') {
15 return <div>Chargement...</div>;
16 }
17
18 return (
19 <div>
20 <h1>Dashboard</h1>
21 <p>Bonjour, {session.user.name} !</p>
22 <p>Ton e-mail : {session.user.email}</p>
23 {session.user.role === 'admin' && (
24 <div>
25 <h2>Panneau d'administration</h2>
26 {/* Contenu réservé aux administrateurs */}
27 </div>
28 )}
29 </div>
30 );
31}2. Récupérer les données de session côté serveur
Dans les composants rendus côté serveur, tu peux utiliser getServerSession :
1// app/profile/page.tsx
2import { getServerSession } from 'next-auth/next';
3import { redirect } from 'next/navigation';
4import { authOptions } from '../api/auth/[...nextauth]/route';
5
6export default async function ProfilePage() {
7 const session = await getServerSession(authOptions);
8
9 if (!session) {
10 redirect('/auth/login?callbackUrl=/profile');
11 }
12
13 return (
14 <div>
15 <h1>Profil</h1>
16 <p>Bonjour, {session.user.name} !</p>
17 <p>Ton e-mail : {session.user.email}</p>
18 {/* Reste du contenu du profil */}
19 </div>
20 );
21}3. Gérer l'état d'authentification
NextAuth.js fournit des fonctions pour gérer l'état d'authentification :
1'use client';
2
3import { signIn, signOut, useSession } from 'next-auth/react';
4
5export function AuthButton() {
6 const { data: session } = useSession();
7
8 if (session) {
9 return (
10 <button
11 onClick={() => signOut({ callbackUrl: '/' })}
12 className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
13 >
14 Se déconnecter
15 </button>
16 );
17 }
18
19 return (
20 <button
21 onClick={() => signIn()}
22 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
23 >
24 Se connecter
25 </button>
26 );
27}Adapter l'interface à NextAuth.js
Nous pouvons créer un composant qui affiche différents éléments d'interface selon l'état d'authentification :
1'use client';
2
3import { useSession } from 'next-auth/react';
4import Link from 'next/link';
5import { AuthButton } from './AuthButton';
6
7export function Navbar() {
8 const { data: session, status } = useSession();
9 const isLoading = status === 'loading';
10
11 return (
12 <nav className="bg-white shadow-md p-4">
13 <div className="container mx-auto flex justify-between items-center">
14 <Link href="/" className="text-xl font-bold">
15 MojaAplikacja
16 </Link>
17
18 <div className="flex items-center space-x-4">
19 <Link href="/" className="hover:text-blue-500">
20 Accueil
21 </Link>
22
23 {session ? (
24 <>
25 <Link href="/dashboard" className="hover:text-blue-500">
26 Dashboard
27 </Link>
28 <Link href="/profile" className="hover:text-blue-500">
29 Profil
30 </Link>
31 {session.user.role === 'admin' && (
32 <Link href="/admin" className="hover:text-blue-500">
33 Admin
34 </Link>
35 )}
36 </>
37 ) : (
38 <Link href="/features" className="hover:text-blue-500">
39 Fonctionnalités
40 </Link>
41 )}
42
43 {!isLoading && (
44 <div className="flex items-center space-x-2">
45 {session && (
46 <div className="flex items-center">
47 {session.user.image && (
48 <img
49 src={session.user.image}
50 alt={session.user.name || 'User'}
51 className="w-8 h-8 rounded-full mr-2"
52 />
53 )}
54 <span className="text-sm font-medium">
55 {session.user.name}
56 </span>
57 </div>
58 )}
59 <AuthButton />
60 </div>
61 )}
62 </div>
63 </div>
64 </nav>
65 );
66}Authentification avec les API Routes
NextAuth.js peut aussi servir à sécuriser les points d'API :
1// app/api/protected-data/route.ts
2import { getServerSession } from 'next-auth/next';
3import { NextResponse } from 'next/server';
4import { authOptions } from '../auth/[...nextauth]/route';
5
6export async function GET() {
7 const session = await getServerSession(authOptions);
8
9 if (!session) {
10 return NextResponse.json(
11 { message: 'Unauthorized access' },
12 { status: 401 }
13 );
14 }
15
16 // Check permissions based on role (optional)
17 if (session.user.role !== 'admin') {
18 return NextResponse.json(
19 { message: 'Insufficient permissions' },
20 { status: 403 }
21 );
22 }
23
24 // Data available only to logged-in users (and admins if we check roles)
25 return NextResponse.json({
26 message: 'Succès',
27 data: {
28 secretContent: 'Ces données sont protégées',
29 timestamp: new Date().toISOString()
30 }
31 });
32}Bonnes pratiques pour la mise en œuvre de NextAuth.js
La sécurité avant tout
- Utilise HTTPS en production
- Génère un NEXTAUTH_SECRET solide
- Ne stocke jamais de données sensibles dans les jetons JWT
Optimisation des performances
- Utilise des stratégies de session adaptées à tes besoins
- Envisage la stratégie JWT pour une meilleure évolutivité
Gestion des erreurs
- Implémente des pages d'erreur personnalisées
- Journalise les erreurs d'authentification pour les analyser
Expérience utilisateur
- Conçois des formulaires de connexion conviviaux
- Permets de basculer facilement entre les fournisseurs
Tests
- Teste différents scénarios d'authentification
- Vérifie comment l'application réagit aux sessions expirées
Scénarios avancés
1. Authentification multi-étapes
Dans certains cas, tu voudras peut-être mettre en place une authentification en plusieurs étapes (par ex. mot de passe + code SMS) :
1// Implémente un CredentialsProvider personnalisé
2CredentialsProvider({
3 name: 'TwoFactor',
4 credentials: {
5 email: { label: "Email", type: "email" },
6 password: { label: "Mot de passe", type: "password" },
7 code: { label: "Code de vérification", type: "text" }
8 },
9 async authorize(credentials) {
10 if (!credentials?.email || !credentials?.password) {
11 return null;
12 }
13
14 // First step: verify password
15 const user = await prisma.user.findUnique({
16 where: { email: credentials.email }
17 });
18
19 if (!user || !await compare(credentials.password, user.password)) {
20 return null;
21 }
22
23 // Second step: verify the verification code
24 if (credentials.code) {
25 const isValidCode = await verifyTwoFactorCode(user.id, credentials.code);
26
27 if (!isValidCode) {
28 return null;
29 }
30 } else {
31 // If the code was not provided and is required
32 if (user.twoFactorEnabled) {
33 // Sending the code to email/phone
34 await sendTwoFactorCode(user);
35
36 // Return a special object so App Router knows we need a code
37 throw new Error('TwoFactorRequired');
38 }
39 }
40
41 // Authentication successful
42 return {
43 id: user.id,
44 email: user.email,
45 name: user.name,
46 role: user.role
47 };
48 }
49})2. Implémenter une logique d'inscription personnalisée
1// app/api/register/route.ts
2import { NextResponse } from 'next/server';
3import { hash } from 'bcrypt';
4import { prisma } from '@/lib/prisma';
5import { z } from 'zod';
6
7// Schéma de validation
8const userSchema = z.object({
9 name: z.string().min(2, 'Le prénom doit comporter au moins 2 caractères'),
10 email: z.string().email('Adresse e-mail invalide'),
11 password: z.string().min(8, 'Le mot de passe doit comporter au moins 8 caractères')
12});
13
14export async function POST(request: Request) {
15 try {
16 const body = await request.json();
17
18 // Validate data
19 const result = userSchema.safeParse(body);
20
21 if (!result.success) {
22 return NextResponse.json(
23 { message: result.error.errors[0].message },
24 { status: 400 }
25 );
26 }
27
28 const { name, email, password } = result.data;
29
30 // Check if user already exists
31 const existingUser = await prisma.user.findUnique({
32 where: { email }
33 });
34
35 if (existingUser) {
36 return NextResponse.json(
37 { message: 'Un utilisateur avec cette adresse e-mail existe déjà' },
38 { status: 409 }
39 );
40 }
41
42 // Hacher le mot de passe
43 const hashedPassword = await hash(password, 10);
44
45 // Créer le nouvel utilisateur
46 const user = await prisma.user.create({
47 data: {
48 name,
49 email,
50 password: hashedPassword,
51 role: 'user' // Rôle par défaut
52 }
53 });
54
55 // Ne pas renvoyer le mot de passe dans la réponse
56 const { password: _, ...userWithoutPassword } = user;
57
58 return NextResponse.json(
59 { message: 'Inscription effectuée avec succès', user: userWithoutPassword },
60 { status: 201 }
61 );
62 } catch (error) {
63 console.error('Registration error:', error);
64 return NextResponse.json(
65 { message: 'Une erreur serveur est survenue' },
66 { status: 500 }
67 );
68 }
69}Résumé
NextAuth.js (Auth.js) est un outil complet pour mettre en œuvre l'authentification dans les applications Next.js. Il offre :
- Flexibilité - prend en charge plusieurs stratégies d'authentification
- Intégration - s'intègre facilement à divers fournisseurs
- Sécurité - applique les meilleures pratiques de sécurité
- Évolutivité - fonctionne avec différentes bases de données et architectures
- Extensibilité - peut être adapté à des besoins spécifiques
Grâce à NextAuth.js, tu peux rapidement mettre en place un système d'authentification complet dans ton application Next.js, tout en gagnant du temps et en réduisant le risque d'erreurs liées à la sécurité.
Dans les modules suivants, nous aborderons des aspects plus avancés de l'authentification, notamment les stratégies d'authentification, la gestion des sessions et des jetons, et la mise en place des rôles et des permissions.
Code de cette leçon : App.tsx
1import React, { useState } from 'react';
2
3// Démo : NextAuth.js / Auth.js - configuration et providers
4interface AuthConfig {
5 provider: string;
6 callbackUrl: string;
7 strategy: 'jwt' | 'database';
8 maxAge: number;
9}
10
11const defaultProviders: AuthConfig[] = [
12 { provider: 'credentials', callbackUrl: '/dashboard', strategy: 'jwt', maxAge: 3600 },
13 { provider: 'google', callbackUrl: '/dashboard', strategy: 'jwt', maxAge: 86400 },
14 { provider: 'github', callbackUrl: '/dashboard', strategy: 'database', maxAge: 604800 },
15];
16
17function AuthConfigBuilder() {
18 const [configs, setConfigs] = useState<AuthConfig[]>(defaultProviders);
19 const [newProvider, setNewProvider] = useState('');
20 const [strategy, setStrategy] = useState<'jwt' | 'database'>('jwt');
21 const [showCode, setShowCode] = useState(false);
22
23 const addProvider = () => {
24 if (!newProvider.trim()) return;
25 setConfigs(prev => [...prev, {
26 provider: newProvider.trim(),
27 callbackUrl: '/dashboard',
28 strategy,
29 maxAge: strategy === 'jwt' ? 3600 : 86400,
30 }]);
31 setNewProvider('');
32 };
33
34 const removeProvider = (index: number) => {
35 setConfigs(prev => prev.filter((_, i) => i !== index));
36 };
37
38 // Mots-clés dans des variables : l’aperçu de l’éditeur retire les lignes import/export du code, même dans le texte du template
39 const kw = { imp: 'import', exp: 'export' };
40 const generatedCode = `// app/api/auth/[...nextauth]/route.ts
41${kw.imp} NextAuth from 'next-auth';
42${configs.map(c => {
43 if (c.provider === 'credentials') return kw.imp + " CredentialsProvider from 'next-auth/providers/credentials';";
44 return kw.imp + " " + c.provider.charAt(0).toUpperCase() + c.provider.slice(1) + "Provider from 'next-auth/providers/" + c.provider + "';";
45}).join('\n')}
46
47${kw.exp} const authOptions = {
48 providers: [
49${configs.map(c => " // " + c.provider + " provider\n // strategy: " + c.strategy).join(',\n')}
50 ],
51 session: {
52 strategy: '${configs[0]?.strategy || 'jwt'}',
53 maxAge: ${configs[0]?.maxAge || 3600},
54 },
55 callbacks: {
56 async jwt({ token, user }) {
57 if (user) token.role = user.role;
58 return token;
59 },
60 async session({ session, token }) {
61 session.user.role = token.role;
62 return session;
63 },
64 },
65};`;
66
67 return (
68 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
69 <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>NextAuth.js / Auth.js Configuration</h1>
70 <p style={{ color: '#8892b0', marginBottom: '24px' }}>Configurateur interactif NextAuth.js - ajoute des providers et génère le code</p>
71
72 <div style={{ display: 'flex', gap: '12px', marginBottom: '24px', flexWrap: 'wrap' }}>
73 <input
74 value={newProvider}
75 onChange={e => setNewProvider(e.target.value)}
76 placeholder="Nom du provider (ex. discord)"
77 style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid #333', background: '#1a1a2e', color: '#fff', flex: 1, minWidth: '200px' }}
78 />
79 <select
80 value={strategy}
81 onChange={e => setStrategy(e.target.value as 'jwt' | 'database')}
82 style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid #333', background: '#1a1a2e', color: '#fff' }}
83 >
84 <option value="jwt">JWT Strategy</option>
85 <option value="database">Database Strategy</option>
86 </select>
87 <button onClick={addProvider} style={{ padding: '8px 16px', borderRadius: '6px', background: '#64ffda', color: '#0f0f23', border: 'none', cursor: 'pointer', fontWeight: 'bold' }}>
88 + Ajouter un Provider
89 </button>
90 </div>
91
92 <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>
93 {configs.map((config, i) => (
94 <div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px', background: '#1a1a2e', borderRadius: '8px', border: '1px solid #333' }}>
95 <div>
96 <strong style={{ color: '#64ffda' }}>{config.provider}</strong>
97 <span style={{ color: '#8892b0', marginLeft: '12px', fontSize: '14px' }}>
98 Strategy: {config.strategy} | MaxAge: {config.maxAge}s
99 </span>
100 </div>
101 <button onClick={() => removeProvider(i)} style={{ background: '#ef4444', color: '#fff', border: 'none', borderRadius: '4px', padding: '4px 8px', cursor: 'pointer' }}>
102 Supprimer
103 </button>
104 </div>
105 ))}
106 </div>
107
108 <button
109 onClick={() => setShowCode(!showCode)}
110 style={{ padding: '10px 20px', borderRadius: '6px', background: showCode ? '#333' : '#7c3aed', color: '#fff', border: 'none', cursor: 'pointer', marginBottom: '16px' }}
111 >
112 {showCode ? 'Masquer le code' : 'Générer le code de configuration'}
113 </button>
114
115 {showCode && (
116 <pre style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', overflow: 'auto', fontSize: '13px', lineHeight: 1.5, border: '1px solid #333' }}>
117 <code style={{ color: '#e0e0e0' }}>{generatedCode}</code>
118 </pre>
119 )}
120 </div>
121 );
122}
123
124export default AuthConfigBuilder;Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. L'authentification signifie :
2. L'autorisation signifie :
Tâches pratiques dans le jeu
- Éditeur de code
Construis une interface avec des sections protégées nécessitant une connexion
- Tri vertical
Place les éléments dans le bon ordre : import 'server-only' → export async → fu
- Clique dans l'ordre
Place les éléments dans le bon ordre : import → NextAuth → from
- Éditeur de code
Crée un composant de connexion avec authentification JWT