
# 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](/docs/sdk/nextjs).

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](/docs/api/management/applications/create)) avec `private_key_jwt` ou un secret client comme authentification du client. Dans un environnement `development`, enregistrez l'URI de redirection `http://localhost/auth/callback` et l'URI de redirection après déconnexion `http://localhost/` ; une URI de boucle locale est enregistrée sans port et correspond à n'importe quel port, comme `3000`.
- Une API enregistrée dans l'environnement ([Créer une API](/docs/api/management/apis/create)) avec l'autorisation `messages:read`. Son `identifier`, par exemple `https://api.example.com`, est l'audience de ses jetons d'accès. Le [guide de démarrage rapide Express](/docs/quickstarts/express) crée une API qui accepte ces jetons.
- Node.js 22.18 ou version ultérieure.

## 1. Installer

```sh
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.

```ts
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.

```ts
/** 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` :

```ts
// 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`.

```ts
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 :

```ts
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.

```tsx
  // 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 :

```ts
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 :

```ts
  // 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.

```tsx
      <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 :

```sh
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.

```ts
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);
```

```sh
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](/docs/sdk/nextjs#limites-actuelles).

## Étapes suivantes

- [SDK Next.js](/docs/sdk/nextjs) : chaque option, le cookie de session et les limites actuelles.
- [Protéger une API Express](/docs/quickstarts/express) : l'API appelée par cette application.
- [Vérification des jetons](/docs/sdk/token-verification) : le vérificateur utilisé par le middleware de votre API.
