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.
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) con
private_key_jwto un client secret come autenticazione del client. In un ambientedevelopment, registra il redirect URIhttp://localhost/auth/callbacke il post-logout redirect URIhttp://localhost/; un URI di loopback viene registrato senza porta e corrisponde a qualsiasi porta, come3000. - Un'API registrata nell'ambiente (Crea un'API) con lo scope
messages:read. Il suoidentifier, per esempiohttps://api.example.com, è l'audience dei suoi access token. La guida rapida Express crea un'API che accetta questi token. - Node.js 22.18 o successivo.
1. Installa
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.
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.
/** 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:
// 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.
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:
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.
// 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:
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:
// 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.
<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:
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.
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
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.
Prossimi passi
- Next.js SDK: ogni opzione, il cookie di sessione e i limiti attuali.
- Proteggi un'API Express: l'API che questa app chiama.
- Verifica dei token: il verificatore sotto il middleware della tua API.