Guide complet de l'authentification Clerk dans Next.js 15 avec organisations et RBAC

Introduction
L'authentification est l'une des fonctionnalités les plus critiques de toute application web, mais la construire de zéro est complexe, sujet aux erreurs et chronophage. Clerk s'est imposé comme la plateforme d'authentification de référence pour les applications Next.js, offrant une solution complète qui va bien au-delà des simples flux de connexion et d'inscription.
Contrairement à NextAuth.js ou aux implémentations JWT personnalisées, Clerk fournit un service entièrement géré avec des composants UI intégrables, l'authentification multi-facteurs, la gestion des organisations, le contrôle d'accès basé sur les rôles (RBAC) et la gestion événementielle par webhooks — le tout prêt à l'emploi.
Dans ce tutoriel, vous allez construire une application SaaS multi-tenant complète avec Clerk et Next.js 15, en implémentant l'authentification, le changement d'organisation, les permissions basées sur les rôles et les routes API protégées.
Ce que vous allez apprendre
- Configurer Clerk dans un projet Next.js 15 App Router
- Implémenter les flux d'inscription, de connexion et de profil utilisateur
- Protéger les routes avec le middleware Clerk
- Créer et gérer des organisations (multi-tenancy)
- Implémenter le contrôle d'accès basé sur les rôles (RBAC)
- Construire des routes API protégées
- Gérer les webhooks pour les événements utilisateur et organisation
- Personnaliser les composants Clerk selon votre design
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 18+ installé sur votre machine
- Des connaissances de base en React et TypeScript
- Une familiarité avec Next.js App Router
- Un compte Clerk (offre gratuite disponible sur clerk.com)
- Un éditeur de code (VS Code recommandé)
Ce que vous allez construire
Vous allez construire un tableau de bord de gestion de projets multi-tenant où :
- Les utilisateurs peuvent s'inscrire et se connecter avec email, Google ou GitHub
- Les utilisateurs peuvent créer et rejoindre des organisations
- Les administrateurs d'organisation peuvent gérer les membres et attribuer des rôles
- Différents rôles (admin, membre, lecteur) voient différents éléments de l'interface
- Les routes API sont protégées en fonction de l'authentification et des rôles
Étape 1 : Créer le projet Next.js
Commencez par créer un nouveau projet Next.js 15 avec TypeScript et Tailwind CSS :
npx create-next-app@latest clerk-saas-app --typescript --tailwind --eslint --app --src-dir
cd clerk-saas-appInstallez le SDK Clerk pour Next.js :
npm install @clerk/nextjsÉtape 2 : Configurer Clerk
Créer une application Clerk
- Allez sur clerk.com et connectez-vous à votre tableau de bord
- Cliquez sur Create application
- Nommez-la "SaaS Dashboard"
- Activez les méthodes de connexion souhaitées (Email, Google, GitHub)
- Copiez vos clés API
Configurer les variables d'environnement
Créez un fichier .env.local à la racine de votre projet :
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_votre_cle_publique
CLERK_SECRET_KEY=sk_test_votre_cle_secrete
# URLs de redirection Clerk
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboardRemplacez les clés fictives par vos véritables clés API Clerk depuis le tableau de bord.
Étape 3 : Ajouter le ClerkProvider
Enveloppez votre application avec le ClerkProvider dans votre layout racine. Cela fournit le contexte d'authentification à tous les composants :
// src/app/layout.tsx
import type { Metadata } from "next";
import { ClerkProvider } from "@clerk/nextjs";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "SaaS Dashboard",
description: "SaaS multi-tenant avec authentification Clerk",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="fr">
<body className={inter.className}>{children}</body>
</html>
</ClerkProvider>
);
}Étape 4 : Configurer le middleware pour la protection des routes
Le middleware Clerk est la colonne vertébrale de la protection des routes. Créez un fichier middleware.ts à la racine de votre projet :
// src/middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPublicRoute = createRouteMatcher([
"/",
"/sign-in(.*)",
"/sign-up(.*)",
"/api/webhooks(.*)",
]);
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect();
}
});
export const config = {
matcher: [
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
"/(api|trpc)(.*)",
],
};Cette configuration garantit que :
- Les routes publiques (accueil, connexion, inscription, webhooks) sont accessibles sans authentification
- Toutes les autres routes nécessitent que l'utilisateur soit connecté
- Les ressources statiques Next.js sont exclues du traitement par le middleware
Étape 5 : Créer les pages d'authentification
Page de connexion
// src/app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";
export default function SignInPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<SignIn
appearance={{
elements: {
rootBox: "mx-auto",
card: "shadow-lg border border-gray-200",
},
}}
/>
</div>
);
}Page d'inscription
// src/app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from "@clerk/nextjs";
export default function SignUpPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<SignUp
appearance={{
elements: {
rootBox: "mx-auto",
card: "shadow-lg border border-gray-200",
},
}}
/>
</div>
);
}Les segments de route catch-all [[...sign-in]] et [[...sign-up]] permettent à Clerk de gérer les flux d'authentification multi-étapes comme la vérification par email et la MFA au sein de la même route.
Étape 6 : Construire le layout du tableau de bord
Créez un layout de tableau de bord protégé avec une barre de navigation affichant les informations utilisateur et le changement d'organisation :
// src/app/dashboard/layout.tsx
import {
OrganizationSwitcher,
UserButton,
} from "@clerk/nextjs";
import Link from "next/link";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gray-50">
<nav className="border-b bg-white px-6 py-3">
<div className="mx-auto flex max-w-7xl items-center justify-between">
<div className="flex items-center gap-6">
<Link href="/dashboard" className="text-xl font-bold">
SaaS Dashboard
</Link>
<div className="flex items-center gap-4">
<Link
href="/dashboard"
className="text-sm text-gray-600 hover:text-gray-900"
>
Projets
</Link>
<Link
href="/dashboard/members"
className="text-sm text-gray-600 hover:text-gray-900"
>
Membres
</Link>
<Link
href="/dashboard/settings"
className="text-sm text-gray-600 hover:text-gray-900"
>
Paramètres
</Link>
</div>
</div>
<div className="flex items-center gap-4">
<OrganizationSwitcher
appearance={{
elements: {
rootBox: "flex items-center",
organizationSwitcherTrigger:
"rounded-md border px-3 py-1.5 text-sm",
},
}}
afterCreateOrganizationUrl="/dashboard"
afterSelectOrganizationUrl="/dashboard"
/>
<UserButton afterSignOutUrl="/" />
</div>
</div>
</nav>
<main className="mx-auto max-w-7xl px-6 py-8">{children}</main>
</div>
);
}Le composant OrganizationSwitcher permet aux utilisateurs de créer de nouvelles organisations et de passer de l'une à l'autre. Le UserButton fournit la gestion du profil et la fonctionnalité de déconnexion.
Étape 7 : Afficher les données utilisateur et organisation
Créez la page principale du tableau de bord qui affiche les données en fonction de l'utilisateur et de l'organisation actuels :
// src/app/dashboard/page.tsx
import { auth, currentUser } from "@clerk/nextjs/server";
export default async function DashboardPage() {
const { orgId, orgRole } = await auth();
const user = await currentUser();
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-bold">
Bienvenue, {user?.firstName || "Utilisateur"}
</h1>
<p className="mt-1 text-gray-500">
Voici un aperçu de votre espace de travail.
</p>
</div>
{orgId ? (
<div className="rounded-lg border bg-white p-6 shadow-sm">
<h2 className="text-lg font-semibold">Organisation actuelle</h2>
<div className="mt-4 grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">ID Organisation</p>
<p className="font-mono text-sm">{orgId}</p>
</div>
<div>
<p className="text-sm text-gray-500">Votre rôle</p>
<p className="font-medium capitalize">
{orgRole?.replace("org:", "")}
</p>
</div>
</div>
</div>
) : (
<div className="rounded-lg border border-dashed border-gray-300 bg-white p-8 text-center">
<h2 className="text-lg font-semibold">
Aucune organisation sélectionnée
</h2>
<p className="mt-2 text-gray-500">
Créez ou rejoignez une organisation pour commencer à collaborer
avec votre équipe.
</p>
</div>
)}
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
<StatCard title="Projets" value="12" />
<StatCard title="Membres" value="8" />
<StatCard title="Tâches actives" value="34" />
</div>
</div>
);
}
function StatCard({ title, value }: { title: string; value: string }) {
return (
<div className="rounded-lg border bg-white p-6 shadow-sm">
<p className="text-sm text-gray-500">{title}</p>
<p className="mt-2 text-3xl font-bold">{value}</p>
</div>
);
}Notez comment auth() est appelé côté serveur pour obtenir l'ID et le rôle de l'organisation actuelle. Ces données sont disponibles sans JavaScript côté client.
Étape 8 : Activer les organisations dans Clerk
Avant d'implémenter les fonctionnalités d'organisation, activez-les dans votre tableau de bord Clerk :
- Allez dans Organizations dans la barre latérale du tableau de bord Clerk
- Cliquez sur Enable organizations
- Sous Roles, vous verrez les rôles par défaut :
org:adminetorg:member - Ajoutez un rôle personnalisé :
org:vieweravec des permissions limitées
Définir les permissions personnalisées
Dans le tableau de bord Clerk sous Organizations, puis Roles and Permissions :
Créez ces permissions :
org:projects:create— Créer de nouveaux projetsorg:projects:read— Voir les projetsorg:projects:update— Modifier les projetsorg:projects:delete— Supprimer les projetsorg:members:manage— Gérer les membres de l'organisation
Attribuez les permissions aux rôles :
| Permission | Admin | Membre | Lecteur |
|---|---|---|---|
| org:projects:create | Oui | Oui | Non |
| org:projects:read | Oui | Oui | Oui |
| org:projects:update | Oui | Oui | Non |
| org:projects:delete | Oui | Non | Non |
| org:members:manage | Oui | Non | Non |
Étape 9 : Implémenter le contrôle d'accès basé sur les rôles
Vérification des permissions côté serveur
Créez une fonction utilitaire pour vérifier les permissions côté serveur :
// src/lib/auth.ts
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export async function requireAuth() {
const session = await auth();
if (!session.userId) {
redirect("/sign-in");
}
return session;
}
export async function requireOrg() {
const session = await requireAuth();
if (!session.orgId) {
redirect("/dashboard");
}
return session;
}
export async function checkPermission(permission: string): Promise<boolean> {
const session = await auth();
if (!session.userId || !session.orgId) {
return false;
}
const hasPermission = await session.has({ permission });
return hasPermission;
}Composants UI basés sur les rôles
Créez un composant qui affiche conditionnellement du contenu en fonction du rôle de l'utilisateur :
// src/components/role-gate.tsx
"use client";
import { useAuth } from "@clerk/nextjs";
type RoleGateProps = {
children: React.ReactNode;
allowedRoles: string[];
fallback?: React.ReactNode;
};
export function RoleGate({
children,
allowedRoles,
fallback = null,
}: RoleGateProps) {
const { orgRole } = useAuth();
if (!orgRole || !allowedRoles.includes(orgRole)) {
return fallback;
}
return children;
}Utilisation du RoleGate
// src/app/dashboard/projects/page.tsx
import { auth } from "@clerk/nextjs/server";
import { RoleGate } from "@/components/role-gate";
export default async function ProjectsPage() {
const { orgId } = await auth();
if (!orgId) {
return (
<div className="text-center py-12">
<p className="text-gray-500">
Sélectionnez une organisation pour voir les projets.
</p>
</div>
);
}
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Projets</h1>
<RoleGate allowedRoles={["org:admin", "org:member"]}>
<button className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700">
Nouveau projet
</button>
</RoleGate>
</div>
<div className="grid gap-4">
<ProjectCard
title="Refonte du site web"
status="En cours"
members={4}
/>
<ProjectCard
title="Application mobile v2"
status="Planification"
members={6}
/>
<ProjectCard
title="Migration API"
status="Terminé"
members={3}
/>
</div>
<RoleGate
allowedRoles={["org:admin"]}
fallback={
<p className="text-sm text-gray-400">
Seuls les administrateurs peuvent gérer les paramètres des projets.
</p>
}
>
<div className="rounded-lg border border-red-200 bg-red-50 p-4">
<h3 className="font-semibold text-red-800">Actions administrateur</h3>
<p className="mt-1 text-sm text-red-600">
Archiver, supprimer ou transférer des projets.
</p>
</div>
</RoleGate>
</div>
);
}
function ProjectCard({
title,
status,
members,
}: {
title: string;
status: string;
members: number;
}) {
return (
<div className="flex items-center justify-between rounded-lg border bg-white p-4 shadow-sm">
<div>
<h3 className="font-semibold">{title}</h3>
<p className="text-sm text-gray-500">{members} membres</p>
</div>
<span className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium">
{status}
</span>
</div>
);
}Étape 10 : Protéger les routes API
Route API protégée basique
// src/app/api/projects/route.ts
import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
export async function GET() {
const { userId, orgId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
}
if (!orgId) {
return NextResponse.json(
{ error: "Aucune organisation sélectionnée" },
{ status: 400 }
);
}
const projects = [
{ id: "1", name: "Refonte du site web", orgId },
{ id: "2", name: "Application mobile v2", orgId },
];
return NextResponse.json({ projects });
}
export async function POST(request: Request) {
const { userId, orgId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
}
if (!orgId) {
return NextResponse.json(
{ error: "Aucune organisation sélectionnée" },
{ status: 400 }
);
}
const session = await auth();
const canCreate = await session.has({ permission: "org:projects:create" });
if (!canCreate) {
return NextResponse.json({ error: "Interdit" }, { status: 403 });
}
const body = await request.json();
const project = {
id: crypto.randomUUID(),
name: body.name,
orgId,
createdBy: userId,
};
return NextResponse.json({ project }, { status: 201 });
}Étape 11 : Gérer les webhooks
Clerk envoie des webhooks pour les événements utilisateur et organisation. C'est essentiel pour synchroniser les données avec votre base de données.
Installer Svix pour la vérification des webhooks
npm install svixCréer le point de terminaison webhook
// src/app/api/webhooks/clerk/route.ts
import { Webhook } from "svix";
import { headers } from "next/headers";
import { WebhookEvent } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const SIGNING_SECRET = process.env.CLERK_WEBHOOK_SECRET;
if (!SIGNING_SECRET) {
throw new Error("Variable CLERK_WEBHOOK_SECRET manquante");
}
const wh = new Webhook(SIGNING_SECRET);
const headerPayload = await headers();
const svixId = headerPayload.get("svix-id");
const svixTimestamp = headerPayload.get("svix-timestamp");
const svixSignature = headerPayload.get("svix-signature");
if (!svixId || !svixTimestamp || !svixSignature) {
return NextResponse.json(
{ error: "En-têtes svix manquants" },
{ status: 400 }
);
}
const payload = await request.json();
const body = JSON.stringify(payload);
let event: WebhookEvent;
try {
event = wh.verify(body, {
"svix-id": svixId,
"svix-timestamp": svixTimestamp,
"svix-signature": svixSignature,
}) as WebhookEvent;
} catch (err) {
console.error("Échec de la vérification du webhook:", err);
return NextResponse.json(
{ error: "Signature invalide" },
{ status: 400 }
);
}
switch (event.type) {
case "user.created": {
const { id, email_addresses, first_name, last_name } = event.data;
console.log("Nouvel utilisateur créé:", id);
// Insérer l'utilisateur dans votre base de données
break;
}
case "user.updated": {
const { id } = event.data;
console.log("Utilisateur mis à jour:", id);
break;
}
case "user.deleted": {
const { id } = event.data;
console.log("Utilisateur supprimé:", id);
break;
}
case "organization.created": {
const { id, name, slug } = event.data;
console.log("Organisation créée:", name);
break;
}
case "organizationMembership.created": {
const { organization, public_user_data, role } = event.data;
console.log("Nouveau membre ajouté à l'org:", organization.id);
break;
}
default:
console.log("Événement webhook non géré:", event.type);
}
return NextResponse.json({ received: true });
}Configurer le webhook dans Clerk
- Allez dans Webhooks dans votre tableau de bord Clerk
- Cliquez sur Add endpoint
- Entrez votre URL :
https://votre-domaine.com/api/webhooks/clerk - Sélectionnez les événements :
user.created,user.updated,user.deleted,organization.created,organizationMembership.created - Copiez le Signing Secret et ajoutez-le à
.env.local:
CLERK_WEBHOOK_SECRET=whsec_votre_secret_webhookPour le développement local, utilisez ngrok :
npx ngrok http 3000Étape 12 : Personnaliser les composants Clerk
Les composants Clerk peuvent être entièrement personnalisés pour correspondre au design de votre application.
Configuration du thème global
// src/app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider
appearance={{
variables: {
colorPrimary: "#2563eb",
colorBackground: "#ffffff",
colorInputBackground: "#f9fafb",
colorInputText: "#111827",
borderRadius: "0.5rem",
fontFamily: "Inter, sans-serif",
},
elements: {
formButtonPrimary:
"bg-blue-600 hover:bg-blue-700 text-sm font-medium",
card: "shadow-md border border-gray-100",
headerTitle: "text-xl font-bold",
headerSubtitle: "text-gray-500",
socialButtonsBlockButton:
"border border-gray-200 hover:bg-gray-50",
formFieldInput:
"border border-gray-300 focus:border-blue-500 focus:ring-blue-500",
footerActionLink: "text-blue-600 hover:text-blue-700",
},
}}
>
<html lang="fr">
<body>{children}</body>
</html>
</ClerkProvider>
);
}Étape 13 : Page de gestion des membres
Construisez une page dédiée à la gestion des membres de l'organisation :
// src/app/dashboard/members/page.tsx
import { auth, clerkClient } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
import { RoleGate } from "@/components/role-gate";
import { InviteMemberForm } from "./invite-form";
export default async function MembersPage() {
const { orgId, userId } = await auth();
if (!orgId) {
redirect("/dashboard");
}
const client = await clerkClient();
const memberships =
await client.organizations.getOrganizationMembershipList({
organizationId: orgId,
});
return (
<div className="space-y-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Membres de l'équipe</h1>
<p className="text-gray-500">
Gérez les accès à cette organisation.
</p>
</div>
<RoleGate allowedRoles={["org:admin"]}>
<InviteMemberForm orgId={orgId} />
</RoleGate>
</div>
<div className="overflow-hidden rounded-lg border bg-white shadow-sm">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
Membre
</th>
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
Rôle
</th>
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
Rejoint le
</th>
<th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{memberships.data.map((member) => (
<tr key={member.id}>
<td className="whitespace-nowrap px-6 py-4">
<div className="flex items-center gap-3">
<img
src={member.publicUserData?.imageUrl}
alt=""
className="h-8 w-8 rounded-full"
/>
<div>
<p className="font-medium">
{member.publicUserData?.firstName}{" "}
{member.publicUserData?.lastName}
</p>
<p className="text-sm text-gray-500">
{member.publicUserData?.identifier}
</p>
</div>
</div>
</td>
<td className="whitespace-nowrap px-6 py-4">
<span className="inline-flex rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800">
{member.role.replace("org:", "")}
</span>
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{new Date(member.createdAt).toLocaleDateString("fr-FR")}
</td>
<td className="whitespace-nowrap px-6 py-4 text-right">
<RoleGate allowedRoles={["org:admin"]}>
{member.publicUserData?.userId !== userId && (
<button className="text-sm text-red-600 hover:text-red-800">
Retirer
</button>
)}
</RoleGate>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}Formulaire d'invitation (composant client)
// src/app/dashboard/members/invite-form.tsx
"use client";
import { useOrganization } from "@clerk/nextjs";
import { useState } from "react";
export function InviteMemberForm({ orgId }: { orgId: string }) {
const { organization } = useOrganization();
const [email, setEmail] = useState("");
const [role, setRole] = useState("org:member");
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState("");
async function handleInvite(e: React.FormEvent) {
e.preventDefault();
setIsLoading(true);
setMessage("");
try {
await organization?.inviteMember({
emailAddress: email,
role: role as "org:admin" | "org:member",
});
setMessage("Invitation envoyée avec succès !");
setEmail("");
} catch (error) {
setMessage("Échec de l'envoi de l'invitation. Veuillez réessayer.");
} finally {
setIsLoading(false);
}
}
return (
<form onSubmit={handleInvite} className="flex items-end gap-3">
<div>
<label className="block text-sm font-medium text-gray-700">
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="collegue@entreprise.com"
className="mt-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Rôle
</label>
<select
value={role}
onChange={(e) => setRole(e.target.value)}
className="mt-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
>
<option value="org:member">Membre</option>
<option value="org:admin">Administrateur</option>
</select>
</div>
<button
type="submit"
disabled={isLoading}
className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? "Envoi..." : "Inviter"}
</button>
{message && (
<p className="text-sm text-green-600">{message}</p>
)}
</form>
);
}Étape 14 : Ajouter l'authentification multi-facteurs
Clerk prend en charge la MFA nativement. Activez-la dans votre tableau de bord :
- Allez dans User and Authentication puis Multi-factor dans le tableau de bord Clerk
- Activez Authenticator application (TOTP)
- Optionnellement, activez la vérification par SMS
Les utilisateurs peuvent ensuite activer la MFA depuis le composant UserProfile :
// src/app/dashboard/settings/page.tsx
import { UserProfile } from "@clerk/nextjs";
export default function SettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold">Paramètres du compte</h1>
<p className="text-gray-500">
Gérez votre profil, votre sécurité et vos préférences.
</p>
</div>
<UserProfile
appearance={{
elements: {
rootBox: "w-full",
card: "shadow-sm border border-gray-200 w-full",
navbar: "border-r border-gray-200",
},
}}
/>
</div>
);
}Tester votre implémentation
1. Démarrer le serveur de développement
npm run dev2. Tester le flux d'authentification
- Naviguez vers
http://localhost:3000— vous devriez voir la page d'accueil publique - Cliquez sur inscription et créez un nouveau compte
- Vérifiez votre email (consultez le mode test Clerk pour la vérification instantanée)
- Après la connexion, vous devriez être redirigé vers
/dashboard
3. Tester les fonctionnalités d'organisation
- Cliquez sur le
OrganizationSwitcherdans la barre de navigation - Créez une nouvelle organisation appelée "Acme Corp"
- Invitez un membre avec un email différent
- Basculez entre le compte personnel et l'organisation
4. Tester le RBAC
- En tant qu'administrateur, vérifiez que vous voyez le bouton "Nouveau projet" et les actions admin
- Connectez-vous avec un compte membre et vérifiez les accès limités
- Essayez d'accéder directement à la route API
/api/projects— elle devrait exiger l'authentification
Dépannage
"auth() a retourné un userId null"
Assurez-vous que votre middleware est correctement configuré et que la route n'est pas marquée comme publique. Vérifiez également que votre CLERK_SECRET_KEY est correctement définie.
"Les fonctionnalités d'organisation ne s'affichent pas"
Activez les organisations dans votre tableau de bord Clerk sous Organizations. Cette fonctionnalité n'est pas activée par défaut.
"Échec de la vérification de la signature du webhook"
Vérifiez le CLERK_WEBHOOK_SECRET dans votre fichier .env.local. Lors de l'utilisation de ngrok, assurez-vous de pointer le webhook Clerk vers votre URL ngrok, pas localhost.
"Erreurs CORS sur les routes API"
Le middleware Clerk gère automatiquement le CORS pour les routes authentifiées. Si vous appelez depuis une origine externe, configurez explicitement les en-têtes CORS dans votre route API.
Prochaines étapes
Maintenant que vous avez un système d'authentification fonctionnel, envisagez :
- Intégration base de données : Connectez les ID utilisateur Clerk à votre base de données avec Drizzle ORM ou Prisma
- Facturation : Ajoutez Stripe pour la gestion des abonnements par organisation
- Journaux d'audit : Suivez tous les événements d'authentification et d'organisation
- Claims personnalisés : Ajoutez des métadonnées personnalisées aux sessions utilisateur
- SSO/SAML : Activez le SSO entreprise pour les grands clients
Conclusion
Dans ce tutoriel, vous avez construit un système d'authentification complet avec Clerk et Next.js 15 incluant :
- L'inscription et la connexion avec plusieurs fournisseurs
- La gestion des organisations pour le multi-tenancy
- Le contrôle d'accès basé sur les rôles avec des permissions personnalisées
- Les routes API protégées avec vérification des permissions
- La gestion des webhooks pour la synchronisation de base de données
- Des composants d'authentification UI personnalisés
- Le support de l'authentification multi-facteurs
Clerk élimine le besoin de construire et maintenir une infrastructure d'authentification complexe, vous permettant de vous concentrer sur les fonctionnalités principales de votre application. Son intégration profonde avec Next.js App Router et les Server Components en fait un choix naturel pour les applications React modernes.
La combinaison des organisations et du RBAC rend cette configuration idéale pour les applications SaaS où la collaboration en équipe et le contrôle d'accès sont essentiels. Avec l'infrastructure gérée par Clerk, vous bénéficiez d'une sécurité de niveau entreprise sans la charge opérationnelle des solutions auto-hébergées.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Securiser une Application Next.js : Le Guide Complet de Cybersecurite pour 2026
Apprenez a securiser votre application Next.js contre les attaques les plus courantes : XSS, CSRF, injection SQL, et plus. Ce guide pratique couvre les en-tetes de securite, l'authentification, la validation des donnees, le rate limiting et les bonnes pratiques OWASP Top 10.

Construire un Agent IA Autonome avec Agentic RAG et Next.js
Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.

Construire un moteur de recherche sémantique avec Next.js 15, OpenAI et Pinecone
Apprenez à construire un moteur de recherche sémantique prêt pour la production avec Next.js 15, OpenAI Embeddings et la base de données vectorielle Pinecone. Ce tutoriel complet couvre la configuration, l'indexation, les requêtes et le déploiement.