Anmeldung in einer Next.js-App hinzufügen

Diese Schnellstartanleitung fügt einer Next.js-App mit App Router und dem SDK @keenauth/nextjs die KeenAuth-Anmeldung hinzu. Ihre App ist der OAuth-Client und hält alle Token auf dem Server; der Browser erhält nur ein verschlüsseltes Sitzungscookie mit HttpOnly-Flag. Sie fügen die Anmelde-Routen hinzu, schützen eine Seite, rufen eine geschützte API mit dem Access Token des Benutzers auf und testen die App offline. Der App Router ist der einzige Router, den das SDK unterstützt. Das SDK wird unter Next.js SDK beschrieben.

Die App hat diese Seiten und Routen:

Route Funktion
/ Öffentliche Startseite: ein Anmeldelink oder der Name des Benutzers und eine Schaltfläche zum Abmelden
/dashboard Erfordert eine Sitzung. Zeigt den Benutzer und die Nachrichten, die Ihre API zurückgibt
/api/messages Erfordert eine Sitzung. Ruft Ihre API mit dem Access Token des Benutzers auf
/auth/login, /auth/callback, /auth/logout Werden vom SDK bereitgestellt

Bevor Sie beginnen

1. Installieren

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

Das SDK ist noch nicht veröffentlicht. Bis dahin verweist das Beispiel auf den SDK-Quellcode in dessen package.json.

2. Konfigurieren

Die App liest ihre Konfiguration ausschließlich aus Umgebungsvariablen. Legen Sie sie in .env.local ab, das Next.js lädt, und committen Sie diese Datei niemals.

Variable Erforderlich Bedeutung
KEENAUTH_ISSUER ja Der Issuer Ihrer Umgebung
KEENAUTH_CLIENT_ID ja Die client_id der Web-Anwendung
KEENAUTH_CLIENT_KEY eine der beiden Der private Schlüssel (ein privater JSON-JWK) für private_key_jwt, empfohlen
KEENAUTH_CLIENT_SECRET eine der beiden Ein Client Secret
APP_BASE_URL ja Die öffentliche URL der App. Das SDK erstellt daraus die Redirect-URI und alle Weiterleitungen. http nur für localhost, 127.0.0.1 oder [::1]
KEENAUTH_COOKIE_SECRET ja Mindestens 32 Zeichen; versiegelt das Sitzungscookie. Erzeugen Sie einen Wert mit openssl rand -base64 32
API_URL ja Die API, die die App aufruft, zum Beispiel http://localhost:8080
KEENAUTH_API_AUDIENCE ja Der Identifier dieser API in KeenAuth

loadConfig liest diese Werte und prüft sie. Die Funktion bricht ab, wenn einer davon fehlt oder wenn beide oder keine der Client-Zugangsdaten gesetzt sind. Ihre Fehlermeldungen enthalten nie ein Geheimnis.

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. SDK-Instanz erstellen

createAuth erstellt die SDK-Instanz, und das SDK prüft beim Erstellen jede Option. getApp erstellt beim ersten Aufruf je Prozess eine Instanz, sodass next build keine Geheimnisse benötigt.

/** 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;
}

Die Konfiguration setzt resource auf den Identifier Ihrer API (RFC 8707). Ohne diesen Wert ist das Access Token nicht für Ihre API bestimmt, und die API lehnt es ab.

4. Anmelde-Routen hinzufügen

Ein einziger Catch-all-Route-Handler stellt /auth/login, /auth/callback und /auth/logout bereit:

// 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);

Die Anmeldung verwendet den Autorisierungscode-Flow mit PKCE und, sofern Ihre Umgebung es anbietet, eine hochgeladene Autorisierungsanfrage (Pushed Authorization Request). Der Callback prüft state, iss und das ID Token. Ein returnTo, das kein Pfad Ihrer App ist, wird ignoriert.

5. Seiten mit dem Proxy schützen

proxy.ts (vor Next.js 16 middleware.ts) erneuert die Sitzung, bevor eine Seite gerendert wird, und schützt die Pfade, die Sie auswählen. Eine Seitenanfrage ohne Sitzung wird mit returnTo zu /auth/login weitergeleitet. Eine Anfrage an /api/… erhält 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;
}

Die Pfade, die eine Sitzung erfordern, stehen in einer einzigen Funktion:

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

Ein Matcher des Proxys kann einen Pfad übersehen. Deshalb prüft jede Seite und jeder Route-Handler, der einen Benutzer benötigt, die Sitzung zusätzlich selbst.

6. Ihre API aufrufen

In einer Server Component lesen Sie die Sitzung und holen das Access Token aus dem Cookie-Store. Das Token bleibt auf dem Server; der Browser erhält nur das 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 enthält die Claims des ID Token, etwa subject, email und name. Die Sitzung enthält nie ein Token.

Der eigentliche Aufruf ist ein einfaches fetch mit einem 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",
  });
}

Ein Route-Handler kann auch Cookies setzen. Deshalb verwendet er updateSession, das ein Access Token erneuert, das bald abläuft:

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

Geben Sie das Access Token nicht an eine Client Component weiter.

7. Abmelden

Die Abmeldung ist ein Formular, das an /auth/logout sendet. Das SDK löscht das Sitzungscookie, widerruft das Refresh Token und schickt den Browser zum Logout-Endpunkt Ihrer Umgebung, der ihn zurück zu Ihrer App leitet. Eine Anfrage, die der Browser als von einer anderen Website stammend kennzeichnet, wird abgelehnt.

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

8. Die App ausführen

Starten Sie die API aus der Express-Schnellstartanleitung (oder eine andere API, die diese Token akzeptiert) und führen Sie die App aus:

npm run dev

Öffnen Sie http://localhost:3000, wählen Sie Sign in, melden Sie sich bei KeenAuth an und öffnen Sie das Dashboard.

9. Ohne Netzwerk testen

Die Tests benötigen kein Netzwerk und kein KeenAuth-Konto. Ein gefälschtes fetch übernimmt die Rolle des Autorisierungsservers (Discovery und die hochgeladene Autorisierungsanfrage), und Node führt die TypeScript-Tests direkt aus.

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

Die Tests prüfen, dass die Konfiguration beim Start validiert wird, dass eine Seite ohne Sitzung zur Anmeldung weiterleitet und ein API-Pfad mit 401 antwortet, dass die Anmeldung eine hochgeladene PKCE-S256-Anfrage sendet und ein versiegeltes HttpOnly-Cookie setzt, dass ein Callback mit unbekanntem State abgelehnt wird, dass eine Abmeldung von einer anderen Website abgelehnt wird und dass das Access Token im Header Authorization an die API übergeben wird.

Die vollständige Anmeldung mit einem echten Browser benötigt eine KeenAuth-Umgebung. Sie ist nicht Teil der Offline-Tests.

Hinter einem Proxy oder Load Balancer

Setzen Sie APP_BASE_URL auf die öffentliche URL der App. Das SDK liest niemals Host oder X-Forwarded-*, um Weiterleitungen zu erstellen. Bei mehreren Server-Instanzen kann ein Refresh Token von zwei Instanzen gleichzeitig erneuert werden; siehe Aktuelle Einschränkungen.

Nächste Schritte