
# Aggiungi l'accesso a un'app Next.js

Questa guida rapida aggiunge l'accesso con KeenAuth a un'app Next.js App Router con l'SDK `@keenauth/nextjs`. La tua app è il client OAuth e mantiene ogni token sul server; il browser riceve solo un cookie di sessione cifrato e HttpOnly. Aggiungi le route di accesso, proteggi una pagina, chiami un'API protetta con l'access token dell'utente e testi l'app offline. L'App Router è l'unico router supportato dall'SDK. L'SDK è descritto in [Next.js SDK](/docs/sdk/nextjs).

L'app ha queste pagine e route:

| Route | Cosa fa |
|---|---|
| `/` | Home page pubblica: un link per accedere, oppure il nome dell'utente e un pulsante per disconnettersi |
| `/dashboard` | Richiede una sessione. Mostra l'utente e i messaggi restituiti dalla tua API |
| `/api/messages` | Richiede una sessione. Chiama la tua API con l'access token dell'utente |
| `/auth/login`, `/auth/callback`, `/auth/logout` | Serviti dall'SDK |

## Prima di iniziare

- Un ambiente KeenAuth. Il suo issuer ha la forma `https://your-environment.eu.keenauth.app`.
- Un'applicazione web nell'ambiente ([Crea un'applicazione](/docs/api/management/applications/create)) con `private_key_jwt` o un client secret come autenticazione del client. In un ambiente `development`, registra il redirect URI `http://localhost/auth/callback` e il post-logout redirect URI `http://localhost/`; un URI di loopback viene registrato senza porta e corrisponde a qualsiasi porta, come `3000`.
- Un'API registrata nell'ambiente ([Crea un'API](/docs/api/management/apis/create)) con lo scope `messages:read`. Il suo `identifier`, per esempio `https://api.example.com`, è l'audience dei suoi access token. La [guida rapida Express](/docs/quickstarts/express) crea un'API che accetta questi token.
- Node.js 22.18 o successivo.

## 1. Installa

```sh
npm install @keenauth/nextjs next@16.4.2 react@19.3.1 react-dom@19.3.1
```

L'SDK non è ancora pubblicato. Finché non lo è, l'esempio rimanda al sorgente dell'SDK nel suo `package.json`.

## 2. Configura

L'app legge la configurazione solo dalle variabili d'ambiente. Inseriscile in `.env.local`, che Next.js carica, e non committare mai quel file.

| Variabile | Obbligatoria | Significato |
|---|---|---|
| `KEENAUTH_ISSUER` | sì | L'issuer del tuo ambiente |
| `KEENAUTH_CLIENT_ID` | sì | Il `client_id` dell'applicazione web |
| `KEENAUTH_CLIENT_KEY` | una delle due | La chiave privata (una JWK privata JSON) per `private_key_jwt`, consigliata |
| `KEENAUTH_CLIENT_SECRET` | una delle due | Un client secret |
| `APP_BASE_URL` | sì | L'URL pubblico dell'app. L'SDK costruisce da esso il redirect URI e ogni redirect. `http` solo per `localhost`, `127.0.0.1` o `[::1]` |
| `KEENAUTH_COOKIE_SECRET` | sì | Almeno 32 caratteri; cifra il cookie di sessione. Generane uno con `openssl rand -base64 32` |
| `API_URL` | sì | L'API che l'app chiama, per esempio `http://localhost:8080` |
| `KEENAUTH_API_AUDIENCE` | sì | L'identifier di quell'API in KeenAuth |

`loadConfig` legge e controlla questi valori. Si ferma se ne manca uno o se sono impostate entrambe o nessuna delle credenziali del client, e i suoi messaggi di errore non contengono mai un 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. Crea l'istanza dell'SDK

`createAuth` crea l'istanza dell'SDK, e l'SDK controlla ogni opzione al momento della creazione. `getApp` crea un'istanza per processo al primo utilizzo, così che `next build` non richiede 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 configurazione imposta `resource` con l'identifier della tua API (RFC 8707). Senza, l'access token non è destinato alla tua API e l'API lo rifiuta.

## 4. Aggiungi le route di accesso

Un unico route handler catch-all serve `/auth/login`, `/auth/callback` e `/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);
```

L'accesso usa il flusso authorization code con PKCE e una richiesta di autorizzazione pushed quando il tuo ambiente la offre. Il callback verifica `state`, `iss` e l'ID token. Un `returnTo` che non è un percorso della tua app viene ignorato.

## 5. Proteggi le pagine con il proxy

`proxy.ts` (`middleware.ts` prima di Next.js 16) aggiorna la sessione prima che una pagina venga renderizzata e protegge i percorsi che scegli. Una richiesta di pagina senza sessione viene reindirizzata a `/auth/login` con `returnTo`; una richiesta a `/api/…` riceve `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;
}
```

I percorsi che richiedono una sessione sono in un'unica funzione:

```ts
export function isProtected(pathname: string): boolean {
  return pathname === "/dashboard" || pathname.startsWith("/dashboard/") || pathname.startsWith("/api/");
}
```

Un matcher del proxy può non coprire un percorso, quindi ogni pagina e ogni route handler che richiede un utente verifica anche la sessione da sé.

## 6. Chiama la tua API

In un Server Component, leggi la sessione e prendi l'access token dal cookie store. Il token resta sul server; il browser riceve solo l'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` contiene i claim dell'ID token, come `subject`, `email` e `name`. La sessione non contiene mai un token.

La chiamata vera e propria è un semplice `fetch` con un bearer token:

```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 route handler può anche impostare cookie, quindi usa `updateSession`, che aggiorna un access token in scadenza:

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

Non passare l'access token a un Client Component.

## 7. Disconnettiti

La disconnessione è un form che invia una POST a `/auth/logout`. L'SDK elimina il cookie di sessione, revoca il refresh token e invia il browser all'endpoint di logout del tuo ambiente, che lo riporta alla tua app. Una richiesta che il browser contrassegna come proveniente da un altro sito viene rifiutata.

```tsx
      <form action="/auth/logout" method="post">
        <button type="submit">Sign out</button>
      </form>
```

## 8. Avvia l'app

Avvia l'API della guida rapida Express (o qualsiasi API che accetta i token) ed esegui l'app:

```sh
npm run dev
```

Apri `http://localhost:3000`, scegli **Sign in**, accedi su KeenAuth e apri la dashboard.

## 9. Testa senza rete

I test non richiedono rete né un account KeenAuth. Un `fetch` finto simula il server di autorizzazione (discovery e richiesta di autorizzazione pushed), e Node esegue direttamente i test 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
```

I test verificano che la configurazione sia validata all'avvio, che una pagina senza sessione reindirizzi all'accesso e un percorso API risponda `401`, che l'accesso invii una richiesta pushed PKCE S256 e imposti un cookie HttpOnly cifrato, che un callback con uno state sconosciuto venga rifiutato, che una disconnessione da un altro sito venga rifiutata e che l'access token arrivi all'API nell'header `Authorization`.

L'accesso completo con un browser reale richiede un ambiente KeenAuth. Non fa parte dei test offline.

## Dietro un proxy o un load balancer

Imposta `APP_BASE_URL` con l'URL pubblico dell'app. L'SDK non legge mai `Host` o `X-Forwarded-*` per costruire i redirect. Con più istanze del server, un refresh token può essere aggiornato da due di loro contemporaneamente; vedi [Limiti attuali](/docs/sdk/nextjs#limiti-attuali).

## Prossimi passi

- [Next.js SDK](/docs/sdk/nextjs): ogni opzione, il cookie di sessione e i limiti attuali.
- [Proteggi un'API Express](/docs/quickstarts/express): l'API che questa app chiama.
- [Verifica dei token](/docs/sdk/token-verification): il verificatore sotto il middleware della tua API.
