Ajouter la connexion à une application Next.js
Ce guide de démarrage rapide ajoute la connexion KeenAuth à une application Next.js App Router avec le SDK @keenauth/nextjs. Votre application est le client OAuth et conserve tous les jetons côté serveur ; le navigateur ne reçoit qu'un cookie de session chiffré et HttpOnly. Vous ajoutez les routes de connexion, protégez une page, appelez une API protégée avec le jeton d'accès de l'utilisateur et testez l'application hors ligne. L'App Router est le seul routeur pris en charge par le SDK. Le SDK est décrit dans SDK Next.js.
L'application comporte les pages et routes suivantes :
| Route | Rôle |
|---|---|
/ |
Page d'accueil publique : un lien de connexion, ou le nom de l'utilisateur et un bouton de déconnexion |
/dashboard |
Nécessite une session. Affiche l'utilisateur et les messages renvoyés par votre API |
/api/messages |
Nécessite une session. Appelle votre API avec le jeton d'accès de l'utilisateur |
/auth/login, /auth/callback, /auth/logout |
Servies par le SDK |
Avant de commencer
- Un environnement KeenAuth. Son émetteur ressemble à
https://your-environment.eu.keenauth.app. - Une application web dans l'environnement (Créer une application) avec
private_key_jwtou un secret client comme authentification du client. Dans un environnementdevelopment, enregistrez l'URI de redirectionhttp://localhost/auth/callbacket l'URI de redirection après déconnexionhttp://localhost/; une URI de boucle locale est enregistrée sans port et correspond à n'importe quel port, comme3000. - Une API enregistrée dans l'environnement (Créer une API) avec l'autorisation
messages:read. Sonidentifier, par exemplehttps://api.example.com, est l'audience de ses jetons d'accès. Le guide de démarrage rapide Express crée une API qui accepte ces jetons. - Node.js 22.18 ou version ultérieure.
1. Installer
npm install @keenauth/nextjs next@16.4.2 react@19.3.1 react-dom@19.3.1
Le SDK n'est pas encore publié. Jusqu'à sa publication, l'exemple renvoie vers le code source du SDK indiqué dans son package.json.
2. Configurer
L'application lit sa configuration uniquement depuis des variables d'environnement. Placez-les dans .env.local, que Next.js charge, et ne validez jamais ce fichier dans un dépôt.
| Variable | Obligatoire | Signification |
|---|---|---|
KEENAUTH_ISSUER |
oui | L'émetteur de votre environnement |
KEENAUTH_CLIENT_ID |
l'un des deux | Le client_id de l'application web |
KEENAUTH_CLIENT_KEY |
l'un des deux | La clé privée (une JWK privée au format JSON) pour private_key_jwt, recommandé |
KEENAUTH_CLIENT_SECRET |
l'un des deux | Un secret client |
APP_BASE_URL |
oui | L'URL publique de l'application. Le SDK construit à partir d'elle l'URI de redirection et toutes les redirections. http uniquement pour localhost, 127.0.0.1 ou [::1] |
KEENAUTH_COOKIE_SECRET |
oui | Au moins 32 caractères ; scelle le cookie de session. Générez-en un avec openssl rand -base64 32 |
API_URL |
oui | L'API appelée par l'application, par exemple http://localhost:8080 |
KEENAUTH_API_AUDIENCE |
oui | L'identifiant de cette API dans KeenAuth |
loadConfig lit et vérifie ces valeurs. Il refuse de continuer si l'une d'elles manque, ou si les deux identifiants client sont définis ou aucun ne l'est, et ses messages d'erreur ne contiennent jamais de secret.
export function loadConfig(env: Record<string, string | undefined>): AppConfig {
const need = (name: string): string => {
const value = env[name];
if (!value) throw new Error(`${name} is required`);
return value;
};
3. Créer l'instance du SDK
createAuth crée l'instance du SDK, et le SDK vérifie chaque option au moment de sa création. getApp crée une seule instance par processus lors de la première utilisation, afin que next build n'ait besoin d'aucun secret.
/** Builds the SDK instance. The options are checked here, so a configuration error stops the app at start-up. */
export function createAuth(config: AppConfig, overrides: Partial<KeenAuthOptions> = {}): KeenAuth {
return createKeenAuth({ ...config.auth, ...overrides });
}
let instance: { auth: KeenAuth; config: AppConfig } | undefined;
/** One instance per process, created on first use so that `next build` needs no secrets. */
export function getApp(): { auth: KeenAuth; config: AppConfig } {
if (!instance) {
const config = loadConfig(process.env);
instance = { auth: createAuth(config), config };
}
return instance;
}
La configuration définit resource avec l'identifiant de votre API (RFC 8707). Sans lui, le jeton d'accès n'est pas destiné à votre API, et celle-ci le rejette.
4. Ajouter les routes de connexion
Un seul gestionnaire de route à capture globale sert /auth/login, /auth/callback et /auth/logout :
// Serves /auth/login, /auth/callback and /auth/logout.
export const GET = (request: Request) => getAuth().handlers.GET(request);
export const POST = (request: Request) => getAuth().handlers.POST(request);
La connexion utilise le flux à code d'autorisation avec PKCE, et une requête d'autorisation poussée lorsque votre environnement la propose. Le callback vérifie state, iss et le jeton ID. Un returnTo qui n'est pas un chemin de votre application est ignoré.
5. Protéger les pages avec le proxy
proxy.ts (middleware.ts avant Next.js 16) renouvelle la session avant le rendu d'une page et protège les chemins que vous choisissez. Une requête de page sans session est redirigée vers /auth/login avec returnTo ; une requête vers /api/… reçoit 401.
export async function proxy(request: NextRequest) {
const result = await getAuth().proxy(request, { protect: isProtected });
if (result.response) return result.response;
const response = NextResponse.next({ request: { headers: result.requestHeaders } });
result.apply(response);
return response;
}
Les chemins nécessitant une session sont définis dans une seule fonction :
export function isProtected(pathname: string): boolean {
return pathname === "/dashboard" || pathname.startsWith("/dashboard/") || pathname.startsWith("/api/");
}
Un filtre de proxy peut manquer un chemin ; c'est pourquoi chaque page et chaque gestionnaire de route qui a besoin d'un utilisateur vérifie aussi la session lui-même.
6. Appeler votre API
Dans un Server Component, lisez la session et prenez le jeton d'accès dans le magasin de cookies. Le jeton reste sur le serveur ; le navigateur ne reçoit que le HTML.
// The proxy has already sent users without a session to the sign-in; check again here, because a matcher can miss a path.
const session = await auth.getSession(cookieStore);
if (!session) return <a href="/auth/login?returnTo=/dashboard">Sign in</a>;
// The access token stays on the server. The browser receives only the HTML below.
const response = await fetchMessages(config.apiUrl, await auth.getAccessToken(cookieStore));
session.user contient les revendications du jeton ID, comme subject, email et name. La session ne contient jamais de jeton.
L'appel lui-même est un simple fetch avec un jeton Bearer :
export async function fetchMessages(apiUrl: string, accessToken: string, fetchImpl: typeof fetch = fetch): Promise<Response> {
return fetchImpl(new URL("/api/messages", apiUrl), {
headers: { Authorization: `Bearer ${accessToken}` },
cache: "no-store",
});
}
Un gestionnaire de route peut aussi définir des cookies ; il utilise donc updateSession, qui renouvelle un jeton d'accès sur le point d'expirer :
// A route handler can set cookies, so updateSession also refreshes an access token that is about to expire.
const { accessToken, apply } = await auth.updateSession(request);
if (!accessToken) return new Response(null, { status: 401 });
Ne transmettez pas le jeton d'accès à un Client Component.
7. Se déconnecter
La déconnexion est un formulaire qui envoie une requête POST à /auth/logout. Le SDK supprime le cookie de session, révoque le jeton de rafraîchissement et envoie le navigateur vers le point de terminaison de déconnexion de votre environnement, qui le renvoie vers votre application. Une requête que le navigateur identifie comme provenant d'un autre site est refusée.
<form action="/auth/logout" method="post">
<button type="submit">Sign out</button>
</form>
8. Lancer l'application
Démarrez l'API du guide de démarrage rapide Express (ou toute autre API qui accepte les jetons), puis lancez l'application :
npm run dev
Ouvrez http://localhost:3000, cliquez sur Sign in, connectez-vous auprès de KeenAuth et ouvrez le tableau de bord.
9. Tester sans réseau
Les tests n'ont besoin ni de réseau ni d'un compte KeenAuth. Un faux fetch joue le rôle du serveur d'autorisation (découverte et requête d'autorisation poussée), et Node exécute directement les tests TypeScript.
test("proxy: a page request without a session is redirected to sign-in with returnTo", async () => {
const { auth } = app();
const result = await auth.proxy(new Request(`${APP}/dashboard?tab=1`), { protect: isProtected });
assert.equal(result.response?.status, 307);
npm test
npm run deps # pinned versions, lockfile sources and licences
Les tests vérifient que la configuration est validée au démarrage, qu'une page sans session redirige vers la connexion et qu'un chemin d'API répond 401, que la connexion envoie une requête poussée PKCE S256 et définit un cookie HttpOnly scellé, qu'un callback avec un state inconnu est refusé, qu'une déconnexion depuis un autre site est refusée et que le jeton d'accès est transmis à l'API dans l'en-tête Authorization.
La connexion complète dans un vrai navigateur nécessite un environnement KeenAuth. Elle ne fait pas partie des tests hors ligne.
Derrière un proxy ou un équilibreur de charge
Définissez APP_BASE_URL avec l'URL publique de l'application. Le SDK ne lit jamais Host ni X-Forwarded-* pour construire les redirections. Avec plusieurs instances serveur, un jeton de rafraîchissement peut être renouvelé par deux d'entre elles en même temps ; voir Limites actuelles.
Étapes suivantes
- SDK Next.js : chaque option, le cookie de session et les limites actuelles.
- Protéger une API Express : l'API appelée par cette application.
- Vérification des jetons : le vérificateur utilisé par le middleware de votre API.