Cours Next.js · Module 7: Authentification et autorisation
Les bases de l'authentification dans les applications Next.js
Dans cette leçon8
L'authentification est un aspect fondamental des applications web modernes. Elle te permet de vérifier l'identité des utilisateurs, ce qui est essentiel pour personnaliser l'expérience, protéger les données et réserver certaines fonctionnalités aux utilisateurs autorisés. Dans ce module, tu vas apprendre les bases de la mise en place de l'authentification dans les applications Next.js, qui combine les avantages du rendu côté serveur (SSR) et du rendu côté client.
Qu'est-ce que l'authentification et pourquoi est-elle importante ?
L'authentification est le processus de vérification de l'identité d'un utilisateur. C'est un élément clé de la sécurité des applications web, car elle :
- Protège les données des utilisateurs - garantit que les informations personnelles ne sont accessibles qu'à leurs propriétaires
- Permet la personnalisation - permet d'adapter l'expérience à un utilisateur précis
- Contrôle l'accès - détermine à quelles ressources et fonctionnalités un utilisateur peut accéder
- Assure la traçabilité - permet de savoir qui a effectué quelles actions et quand
Authentification vs. autorisation
Il est important de distinguer deux notions liées mais distinctes :
- Authentification - vérifier l'identité de l'utilisateur (« Es-tu bien celui que tu prétends être ? »)
- Autorisation - déterminer ce qu'un utilisateur authentifié peut faire (« As-tu la permission de faire cela ? »)
Dans bien des cas, les deux processus sont liés, mais ils remplissent des fonctions différentes dans l'architecture de sécurité.
Les spécificités de l'authentification dans Next.js
Next.js propose un modèle de rendu hybride qui a un impact important sur la mise en œuvre de l'authentification :
- Server-Side Rendering (SSR) - permet de vérifier l'authentification avant d'envoyer la page au client
- Static Site Generation (SSG) - nécessite une vérification supplémentaire côté client pour le contenu protégé
- App Router - fournit de nouveaux mécanismes dédiés à la gestion de l'authentification
Les principaux modèles d'authentification
Plusieurs grands modèles d'authentification peuvent être appliqués dans les applications Next.js :
1. Authentification basée sur les sessions
Une approche traditionnelle qui repose sur le maintien de l'état de la session côté serveur :
1// Exemple de route API pour une connexion basée sur les sessions
2import { NextApiRequest, NextApiResponse } from 'next';
3import { getServerSession } from 'next-auth/next';
4import { authOptions } from '../auth/[...nextauth]';
5
6export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 const session = await getServerSession(req, res, authOptions);
8
9 if (!session) {
10 return res.status(401).json({ message: 'Unauthorized' });
11 }
12
13 // Utilisateur authentifié, on peut continuer
14 return res.status(200).json({ user: session.user });
15}2. Authentification basée sur les jetons (JWT)
Une approche populaire qui utilise les JWT (JSON Web Tokens) :
1// Exemple de route API pour la vérification d'un jeton JWT
2import { NextApiRequest, NextApiResponse } from 'next';
3import jwt from 'jsonwebtoken';
4
5export default function handler(req: NextApiRequest, res: NextApiResponse) {
6 const { authorization } = req.headers;
7
8 if (!authorization || !authorization.startsWith('Bearer ')) {
9 return res.status(401).json({ message: 'Unauthorized' });
10 }
11
12 const token = authorization.replace('Bearer ', '');
13
14 try {
15 const decoded = jwt.verify(token, process.env.JWT_SECRET!);
16 // Token valid, can proceed
17 return res.status(200).json({ user: decoded });
18 } catch (error) {
19 return res.status(401).json({ message: 'Invalid token' });
20 }
21}3. Authentification avec des fournisseurs externes (OAuth)
En utilisant des services externes comme Google, Facebook ou GitHub :
1// Example NextAuth.js configuration with Google authentication
2import NextAuth from 'next-auth';
3import GoogleProvider from 'next-auth/providers/google';
4
5export default NextAuth({
6 providers: [
7 GoogleProvider({
8 clientId: process.env.GOOGLE_CLIENT_ID!,
9 clientSecret: process.env.GOOGLE_CLIENT_SECRET!
10 })
11 ],
12 // Configuration supplémentaire...
13});Implémenter l'authentification dans l'App Router de Next.js
Dans les versions récentes de Next.js (13+) avec l'App Router, l'approche de l'authentification a été améliorée. Voici les étapes de base :
1. Configuration du fournisseur d'authentification
Tout d'abord, nous devons configurer le fournisseur d'authentification. Dans cet exemple, nous allons utiliser Auth.js (anciennement NextAuth.js) :
1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from 'next-auth';
3import CredentialsProvider from 'next-auth/providers/credentials';
4import { compare } from 'bcrypt';
5import { prisma } from '@/lib/prisma';
6
7export const authOptions = {
8 providers: [
9 CredentialsProvider({
10 name: 'Credentials',
11 credentials: {
12 email: { label: "Email", type: "email" },
13 password: { label: "Password", type: "password" }
14 },
15 async authorize(credentials) {
16 if (!credentials?.email || !credentials?.password) {
17 return null;
18 }
19
20 const user = await prisma.user.findUnique({
21 where: {
22 email: credentials.email
23 }
24 });
25
26 if (!user) {
27 return null;
28 }
29
30 const isPasswordValid = await compare(
31 credentials.password,
32 user.password
33 );
34
35 if (!isPasswordValid) {
36 return null;
37 }
38
39 return {
40 id: user.id,
41 email: user.email,
42 name: user.name
43 };
44 }
45 })
46 ],
47 pages: {
48 signIn: '/login',
49 signOut: '/logout',
50 error: '/login',
51 },
52 session: {
53 strategy: 'jwt'
54 }
55};
56
57const handler = NextAuth(authOptions);
58export { handler as GET, handler as POST };2. Créer les composants de connexion et d'inscription
Ensuite, nous avons besoin d'une interface utilisateur pour la connexion :
1// app/login/page.tsx
2'use client';
3
4import { useState } from 'react';
5import { signIn } from 'next-auth/react';
6import { useRouter } from 'next/navigation';
7
8export default function LoginPage() {
9 const router = useRouter();
10 const [email, setEmail] = useState('');
11 const [password, setPassword] = useState('');
12 const [error, setError] = useState('');
13 const [loading, setLoading] = useState(false);
14
15 async function handleSubmit(e: React.FormEvent) {
16 e.preventDefault();
17 setLoading(true);
18 setError('');
19
20 try {
21 const result = await signIn('credentials', {
22 redirect: false,
23 email,
24 password
25 });
26
27 if (result?.error) {
28 setError('E-mail ou mot de passe invalide');
29 } else {
30 router.push('/dashboard');
31 router.refresh();
32 }
33 } catch (error) {
34 setError('Une erreur est survenue lors de la connexion');
35 } finally {
36 setLoading(false);
37 }
38 }
39
40 return (
41 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
42 <h1 className="text-2xl font-bold mb-6">Connexion</h1>
43
44 {error && (
45 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
46 {error}
47 </div>
48 )}
49
50 <form onSubmit={handleSubmit} className="space-y-4">
51 <div>
52 <label htmlFor="email" className="block text-sm font-medium mb-1">
53 Email
54 </label>
55 <input
56 id="email"
57 type="email"
58 value={email}
59 onChange={(e) => setEmail(e.target.value)}
60 required
61 className="w-full px-3 py-2 border border-gray-300 rounded"
62 />
63 </div>
64
65 <div>
66 <label htmlFor="password" className="block text-sm font-medium mb-1">
67 Mot de passe
68 </label>
69 <input
70 id="password"
71 type="password"
72 value={password}
73 onChange={(e) => setPassword(e.target.value)}
74 required
75 className="w-full px-3 py-2 border border-gray-300 rounded"
76 />
77 </div>
78
79 <button
80 type="submit"
81 disabled={loading}
82 className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 disabled:opacity-50"
83 >
84 {loading ? 'Connexion en cours...' : 'Se connecter'}
85 </button>
86 </form>
87 </div>
88 );
89}Et une page d'inscription :
1// app/register/page.tsx
2'use client';
3
4import { useState } from 'react';
5import { useRouter } from 'next/navigation';
6
7export default function RegisterPage() {
8 const router = useRouter();
9 const [name, setName] = useState('');
10 const [email, setEmail] = useState('');
11 const [password, setPassword] = useState('');
12 const [confirmPassword, setConfirmPassword] = useState('');
13 const [error, setError] = useState('');
14 const [loading, setLoading] = useState(false);
15
16 async function handleSubmit(e: React.FormEvent) {
17 e.preventDefault();
18 setLoading(true);
19 setError('');
20
21 // Valider le formulaire
22 if (password !== confirmPassword) {
23 setError('Les mots de passe ne correspondent pas');
24 setLoading(false);
25 return;
26 }
27
28 try {
29 const response = await fetch('/api/register', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ name, email, password })
33 });
34
35 const data = await response.json();
36
37 if (!response.ok) {
38 throw new Error(data.message || "Une erreur est survenue lors de l'inscription");
39 }
40
41 // Inscription réussie, redirection vers la page de connexion
42 router.push('/login?registered=true');
43 } catch (error) {
44 setError(error instanceof Error ? error.message : 'Une erreur est survenue');
45 } finally {
46 setLoading(false);
47 }
48 }
49
50 return (
51 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
52 <h1 className="text-2xl font-bold mb-6">Inscription</h1>
53
54 {error && (
55 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
56 {error}
57 </div>
58 )}
59
60 <form onSubmit={handleSubmit} className="space-y-4">
61 <div>
62 <label htmlFor="name" className="block text-sm font-medium mb-1">
63 Nom complet
64 </label>
65 <input
66 id="name"
67 type="text"
68 value={name}
69 onChange={(e) => setName(e.target.value)}
70 required
71 className="w-full px-3 py-2 border border-gray-300 rounded"
72 />
73 </div>
74
75 <div>
76 <label htmlFor="email" className="block text-sm font-medium mb-1">
77 Email
78 </label>
79 <input
80 id="email"
81 type="email"
82 value={email}
83 onChange={(e) => setEmail(e.target.value)}
84 required
85 className="w-full px-3 py-2 border border-gray-300 rounded"
86 />
87 </div>
88
89 <div>
90 <label htmlFor="password" className="block text-sm font-medium mb-1">
91 Mot de passe
92 </label>
93 <input
94 id="password"
95 type="password"
96 value={password}
97 onChange={(e) => setPassword(e.target.value)}
98 required
99 minLength={8}
100 className="w-full px-3 py-2 border border-gray-300 rounded"
101 />
102 </div>
103
104 <div>
105 <label htmlFor="confirmPassword" className="block text-sm font-medium mb-1">
106 Confirmer le mot de passe
107 </label>
108 <input
109 id="confirmPassword"
110 type="password"
111 value={confirmPassword}
112 onChange={(e) => setConfirmPassword(e.target.value)}
113 required
114 className="w-full px-3 py-2 border border-gray-300 rounded"
115 />
116 </div>
117
118 <button
119 type="submit"
120 disabled={loading}
121 className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 disabled:opacity-50"
122 >
123 {loading ? 'Inscription en cours...' : "S'inscrire"}
124 </button>
125 </form>
126 </div>
127 );
128}3. Implémenter le point d'API pour l'inscription
Pour permettre l'inscription, nous avons besoin d'un point d'API :
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 }
52 });
53
54 // Renvoyer la réponse sans le mot de passe
55 const { password: _, ...userWithoutPassword } = user;
56
57 return NextResponse.json(
58 { message: 'Inscription effectuée avec succès', user: userWithoutPassword },
59 { status: 201 }
60 );
61 } catch (error) {
62 console.error('Registration error:', error);
63 return NextResponse.json(
64 { message: 'Une erreur serveur est survenue' },
65 { status: 500 }
66 );
67 }
68}4. Protéger les pages réservées
Pour protéger les pages qui ne doivent être accessibles qu'aux utilisateurs connectés, nous pouvons utiliser un middleware :
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4import { getToken } from 'next-auth/jwt';
5
6export async function middleware(request: NextRequest) {
7 const token = await getToken({ req: request });
8
9 // Check if user is logged in
10 if (!token) {
11 const url = new URL('/login', request.url);
12 url.searchParams.set('callbackUrl', request.nextUrl.pathname);
13 return NextResponse.redirect(url);
14 }
15
16 return NextResponse.next();
17}
18
19// Define paths that should be protected
20export const config = {
21 matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*']
22};5. Afficher les données de l'utilisateur et gérer les sessions
Pour utiliser les informations de l'utilisateur connecté dans un composant, nous pouvons employer le hook useSession :
1// components/ProfileSection.tsx
2'use client';
3
4import { useSession } from 'next-auth/react';
5import { signOut } from 'next-auth/react';
6
7export default function ProfileSection() {
8 const { data: session, status } = useSession();
9
10 if (status === 'loading') {
11 return <div>Chargement...</div>;
12 }
13
14 if (status === 'unauthenticated' || !session) {
15 return <div>Tu n'es pas connecté</div>;
16 }
17
18 return (
19 <div className="p-4 bg-white rounded shadow">
20 <h2 className="text-xl font-bold mb-4">Ton profil</h2>
21 <p>Bonjour, {session.user?.name || 'utilisateur'} !</p>
22 <p>Email: {session.user?.email}</p>
23
24 <button
25 onClick={() => signOut({ callbackUrl: '/' })}
26 className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
27 >
28 Se déconnecter
29 </button>
30 </div>
31 );
32}6. Initialiser le SessionProvider pour toute l'application
Pour que l'authentification fonctionne correctement, nous devons ajouter SessionProvider au niveau du layout :
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}Mécanismes de sécurité
Lorsque tu mets en place l'authentification, il est important de garder à l'esprit plusieurs mécanismes de sécurité essentiels :
1. Stockage sécurisé des mots de passe
Ne stocke jamais les mots de passe en clair ! Utilise toujours des fonctions de hachage comme bcrypt :
1import { hash, compare } from 'bcrypt';
2
3// Hachage du mot de passe lors de l'inscription
4const hashedPassword = await hash(password, 10);
5
6// Vérification du mot de passe lors de la connexion
7const isValid = await compare(password, hashedPassword);2. Protection contre les attaques CSRF
Le Cross-Site Request Forgery (CSRF) est un type d'attaque qui exploite la session d'un utilisateur authentifié pour effectuer des actions non autorisées. Next.js et NextAuth.js fournissent certaines protections par défaut, mais il vaut la peine d'assurer une protection supplémentaire :
1// Adding a CSRF token to the form
2export default function LoginForm() {
3 const csrfToken = await getCsrfToken();
4
5 return (
6 <form method="post" action="/api/auth/callback/credentials">
7 <input name="csrfToken" type="hidden" defaultValue={csrfToken} />
8 {/* Reste du formulaire */}
9 </form>
10 );
11}3. Mise en place du HTTPS
L'utilisation de connexions HTTPS chiffrées est essentielle pour protéger les identifiants d'authentification et les jetons de session. En production, assure-toi que ton application n'est accessible que via HTTPS.
4. Expiration des jetons JWT
Si tu utilises des jetons JWT, définis une durée d'expiration appropriée pour limiter les risques en cas de vol du jeton :
1// JWT token expiration configuration in NextAuth.js
2export const authOptions = {
3 // ... autres options
4 session: {
5 strategy: 'jwt',
6 maxAge: 30 * 24 * 60 * 60, // 30 jours
7 },
8 jwt: {
9 secret: process.env.JWT_SECRET,
10 maxAge: 30 * 24 * 60 * 60, // 30 jours
11 }
12};Défis fréquents et solutions
Problème : forcer l'authentification sur les pages statiques (SSG)
Sécuriser des pages générées statiquement au moment du build peut être un défi :
Solution : Ajoute une couche de vérification supplémentaire côté client :
1'use client';
2
3import { useSession } from 'next-auth/react';
4import { useRouter } from 'next/navigation';
5import { useEffect } from 'react';
6
7export default function ProtectedPage() {
8 const { status } = useSession();
9 const router = useRouter();
10
11 useEffect(() => {
12 if (status === 'unauthenticated') {
13 router.push('/login');
14 }
15 }, [status, router]);
16
17 if (status === 'loading') {
18 return <div>Chargement...</div>;
19 }
20
21 if (status === 'unauthenticated') {
22 return null; // Au lieu d'afficher le contenu, on redirige vers la page de connexion
23 }
24
25 // Contenu réel de la page protégée
26 return (
27 <div>
28 <h1>Page protégée</h1>
29 {/* Reste du contenu */}
30 </div>
31 );
32}Problème : différents rôles d'utilisateur
Parfois, nous avons besoin de différents niveaux d'accès pour les utilisateurs :
Solution : Étends le jeton/la session avec l'information de rôle et mets en place une vérification des rôles :
1// Extending NextAuth types
2declare module 'next-auth' {
3 interface Session {
4 user: {
5 id: string;
6 name?: string | null;
7 email?: string | null;
8 image?: string | null;
9 role: string;
10 };
11 }
12
13 interface User {
14 id: string;
15 name?: string | null;
16 email?: string | null;
17 image?: string | null;
18 role: string;
19 }
20}
21
22// Using role information
23function canAccessAdmin(session: Session | null) {
24 return session?.user?.role === 'admin';
25}
26
27// Dans le composant
28const { data: session } = useSession();
29const isAdmin = session?.user?.role === 'admin';
30
31if (!isAdmin) {
32 return <div>Accès refusé. Cette section est réservée aux administrateurs.</div>;
33}Résumé
L'authentification dans Next.js peut être mise en œuvre de bien des façons, mais les approches les plus courantes sont :
- NextAuth.js / Auth.js - une solution complète offrant l'intégration avec de nombreux fournisseurs d'authentification
- Solution JWT personnalisée - pour des besoins non standards
- Services externes - par ex. Firebase Auth, Auth0, Clerk
Le choix de la bonne solution dépend des besoins précis de ton application, tels que :
- Le besoin d'intégration avec des fournisseurs externes (Google, Facebook, etc.)
- Les exigences de propriété des données
- La complexité du modèle de permissions et de rôles
- L'évolutivité
Dans les modules suivants, nous examinerons des aspects plus avancés de l'authentification, notamment la mise en œuvre détaillée de NextAuth.js, la gestion des sessions et des jetons, la protection des routes avec un middleware et les questions de sécurité.
Code de cette leçon : App.tsx
1// Démo : Les bases de l'authentification dans les applications Next.js
2// Overview of auth strategies: JWT, Session, OAuth
3import React, { useState } from 'react';
4
5// L'authentification dans Next.js :
6// 1. JWT (JSON Web Token) - token dans le Authorization header
7// 2. Session-based - cookie avec session ID, données sur le serveur
8// 3. OAuth - delegation to external provider (Google, GitHub)
9// 4. Auth.js (NextAuth) - la bibliothèque la plus populaire
10
11interface AuthStrategy {
12 name: string;
13 description: string;
14 flow: string[];
15 pros: string[];
16 cons: string[];
17 color: string;
18 nextjsCode: string;
19}
20
21const strategies: AuthStrategy[] = [
22 {
23 name: 'JWT (Stateless)',
24 description: 'Token containing encoded user data. Server does not store session state.',
25 flow: ['User logs in (email + password)', 'Server generates JWT token', 'Token sent in every request (Header)', 'Server verifies token signature'],
26 pros: ['Scalable - no server state', 'Works with microservices', 'Easy to implement'],
27 cons: ['Cannot be easily revoked', 'Token can be large', 'Risk of token theft'],
28 color: '#ff9800',
29 nextjsCode: "// middleware.ts\nimport { jwtVerify } from 'jose';\n\nexport async function middleware(req) {\n const token = req.headers.get('authorization');\n const { payload } = await jwtVerify(token, secret);\n // payload.userId, payload.role...\n}",
30 },
31 {
32 name: 'Session (Stateful)',
33 description: 'Le serveur stocke les données de session. Le client reçoit un cookie avec l\'ID de session.',
34 flow: ['User logs in', 'Server creates session in database/Redis', 'Cookie with session ID sent to client', 'On every request server checks session'],
35 pros: ['Easy revocation (delete session)', 'Less data in cookie', 'More secure (HttpOnly cookies)'],
36 cons: ['Nécessite un storage (Redis/DB)', 'Scalabilité plus difficile', 'Sticky sessions'],
37 color: '#64ffda',
38 nextjsCode: "// auth.ts (Auth.js v5)\nimport NextAuth from 'next-auth';\nimport Credentials from 'next-auth/providers/credentials';\n\nexport const { auth, signIn, signOut } = NextAuth({\n providers: [Credentials({...})],\n session: { strategy: 'database' },\n});",
39 },
40 {
41 name: 'OAuth 2.0',
42 description: 'Authentication delegation to external provider (Google, GitHub, etc.).',
43 flow: ['User clicks "Sign in with Google"', 'Redirect to Google login', 'Google confirms identity', 'Callback with authorization code', 'Server exchanges code for token'],
44 pros: ['No password management', 'Trusted provider', 'Fast login for user'],
45 cons: ['Dependency on external service', 'Complex flow (redirects)', 'Requires app registration'],
46 color: '#7c4dff',
47 nextjsCode: "// auth.ts\nimport NextAuth from 'next-auth';\nimport Google from 'next-auth/providers/google';\nimport GitHub from 'next-auth/providers/github';\n\nexport const { handlers, auth } = NextAuth({\n providers: [Google, GitHub],\n});\n\n// app/api/auth/[...nextauth]/route.ts\nexport const { GET, POST } = handlers;",
48 },
49];
50
51function FlowDiagram({ steps, color }: { steps: string[]; color: string }) {
52 return (
53 <div style={{ display: 'grid', gap: '8px', marginTop: '12px' }}>
54 {steps.map((step, i) => (
55 <div key={i} style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
56 <div style={{
57 width: '28px', height: '28px', borderRadius: '50%',
58 background: `${color}20`, color, flexShrink: 0,
59 display: 'flex', alignItems: 'center', justifyContent: 'center',
60 fontSize: '0.8rem', fontWeight: 'bold',
61 }}>{i + 1}</div>
62 <span style={{ color: '#b0bec5', fontSize: '0.85rem' }}>{step}</span>
63 </div>
64 ))}
65 </div>
66 );
67}
68
69export default function AuthIntroduction() {
70 const [activeStrategy, setActiveStrategy] = useState(0);
71 const s = strategies[activeStrategy];
72
73 return (
74 <div style={{
75 background: '#0f0f23', minHeight: '100vh', padding: '24px',
76 color: '#fff', fontFamily: 'system-ui, sans-serif',
77 }}>
78 <h1 style={{ color: '#64ffda', marginBottom: '4px' }}>Authentication in Next.js</h1>
79 <p style={{ color: '#b0bec5', marginBottom: '20px' }}>
80 JWT vs Session vs OAuth - strategy comparison
81 </p>
82
83 <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '10px', marginBottom: '24px' }}>
84 {strategies.map((st, i) => (
85 <button key={st.name} onClick={() => setActiveStrategy(i)} style={{
86 background: i === activeStrategy ? `${st.color}15` : 'rgba(255,255,255,0.02)',
87 border: `2px solid ${i === activeStrategy ? st.color : 'rgba(255,255,255,0.08)'}`,
88 color: i === activeStrategy ? st.color : '#78909c',
89 padding: '14px', borderRadius: '10px', cursor: 'pointer',
90 textAlign: 'center', fontWeight: 500,
91 }}>
92 {st.name}
93 </button>
94 ))}
95 </div>
96
97 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
98 <div>
99 <div style={{
100 background: `${s.color}08`, border: `1px solid ${s.color}30`,
101 borderRadius: '12px', padding: '20px',
102 }}>
103 <h3 style={{ color: s.color, marginBottom: '8px' }}>{s.name}</h3>
104 <p style={{ color: '#b0bec5', fontSize: '0.9rem', lineHeight: 1.5, marginBottom: '0' }}>
105 {s.description}
106 </p>
107 <FlowDiagram steps={s.flow} color={s.color} />
108 </div>
109
110 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: '16px' }}>
111 <div style={{ background: 'rgba(76,175,80,0.08)', borderRadius: '10px', padding: '14px' }}>
112 <h4 style={{ color: '#4caf50', marginBottom: '8px', fontSize: '0.85rem' }}>Avantages</h4>
113 {s.pros.map(p => (
114 <p key={p} style={{ color: '#b0bec5', fontSize: '0.8rem', margin: '4px 0' }}>+ {p}</p>
115 ))}
116 </div>
117 <div style={{ background: 'rgba(244,67,54,0.08)', borderRadius: '10px', padding: '14px' }}>
118 <h4 style={{ color: '#f44336', marginBottom: '8px', fontSize: '0.85rem' }}>Inconvénients</h4>
119 {s.cons.map(c => (
120 <p key={c} style={{ color: '#b0bec5', fontSize: '0.8rem', margin: '4px 0' }}>- {c}</p>
121 ))}
122 </div>
123 </div>
124 </div>
125
126 <div style={{
127 background: 'rgba(0,0,0,0.3)', borderRadius: '12px', padding: '20px',
128 }}>
129 <h4 style={{ color: s.color, marginBottom: '12px' }}>Implémentation dans Next.js</h4>
130 <pre style={{
131 margin: 0, color: '#b0bec5', fontSize: '0.8rem',
132 lineHeight: 1.6, whiteSpace: 'pre-wrap',
133 }}>
134 {s.nextjsCode}
135 </pre>
136 </div>
137 </div>
138 </div>
139 );
140}Tu as repéré une erreur dans cette leçon ?