Añadir inicio de sesión a una aplicación Next.js
Este inicio rápido añade el inicio de sesión de KeenAuth a una aplicación Next.js con App Router, usando el SDK @keenauth/nextjs. Su aplicación es el cliente OAuth y guarda todos los tokens en el servidor; el navegador recibe solo una cookie de sesión cifrada y HttpOnly. Usted añade las rutas de inicio de sesión, protege una página, llama a una API protegida con el token de acceso del usuario y prueba la aplicación sin conexión. App Router es el único enrutador que admite el SDK. El SDK se describe en SDK de Next.js.
La aplicación tiene estas páginas y rutas:
| Ruta | Qué hace |
|---|---|
/ |
Página de inicio pública: un enlace para iniciar sesión, o el nombre del usuario y un botón para cerrar sesión |
/dashboard |
Requiere una sesión. Muestra el usuario y los mensajes que devuelve su API |
/api/messages |
Requiere una sesión. Llama a su API con el token de acceso del usuario |
/auth/login, /auth/callback, /auth/logout |
Servidas por el SDK |
Antes de empezar
- Un entorno de KeenAuth. Su emisor tiene un aspecto como
https://your-environment.eu.keenauth.app. - Una aplicación web en el entorno (Crear una aplicación) con
private_key_jwto un secreto de cliente como autenticación del cliente. En un entornodevelopment, registre el URI de redirecciónhttp://localhost/auth/callbacky el URI de redirección tras cerrar sesiónhttp://localhost/; un URI de bucle invertido se registra sin puerto y coincide con cualquier puerto, como3000. - Una API registrada en el entorno (Crear una API) con el ámbito
messages:read. Suidentifier, por ejemplohttps://api.example.com, es la audiencia de sus tokens de acceso. El inicio rápido de Express crea una API que acepta estos tokens. - Node.js 22.18 o posterior.
1. Instalar
npm install @keenauth/nextjs next@16.4.2 react@19.3.1 react-dom@19.3.1
El SDK todavía no está publicado. Hasta entonces, el ejemplo enlaza con el código fuente del SDK en su package.json.
2. Configurar
La aplicación lee su configuración solo desde variables de entorno. Colóquelas en .env.local, que Next.js carga, y no suba nunca ese archivo al control de versiones.
| Variable | Obligatoria | Significado |
|---|---|---|
KEENAUTH_ISSUER |
sí | El emisor de su entorno |
KEENAUTH_CLIENT_ID |
sí | El client_id de la aplicación web |
KEENAUTH_CLIENT_KEY |
una de las dos | La clave privada (una JWK privada en JSON) para private_key_jwt, recomendada |
KEENAUTH_CLIENT_SECRET |
una de las dos | Un secreto de cliente |
APP_BASE_URL |
sí | La URL pública de la aplicación. El SDK construye desde ella el URI de redirección y todas las redirecciones. http solo para localhost, 127.0.0.1 o [::1] |
KEENAUTH_COOKIE_SECRET |
sí | Al menos 32 caracteres; sella la cookie de sesión. Genere uno con openssl rand -base64 32 |
API_URL |
sí | La API a la que llama la aplicación, por ejemplo http://localhost:8080 |
KEENAUTH_API_AUDIENCE |
sí | El identificador de esa API en KeenAuth |
loadConfig lee y comprueba estos valores. Se niega a continuar si falta alguno o si se definen ambas credenciales de cliente o ninguna, y sus mensajes de error nunca contienen un secreto.
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. Crear la instancia del SDK
createAuth crea la instancia del SDK, y el SDK comprueba cada opción en el momento de crearla. getApp crea una instancia por proceso en el primer uso, de modo que next build no necesita secretos.
/** 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 configuración establece resource con el identificador de su API (RFC 8707). Sin él, el token de acceso no está destinado a su API y la API lo rechaza.
4. Añadir las rutas de inicio de sesión
Un único controlador de ruta comodín sirve /auth/login, /auth/callback y /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);
El inicio de sesión usa el flujo de código de autorización con PKCE y una solicitud de autorización enviada previamente (pushed authorization request) cuando su entorno la ofrece. El callback comprueba state, iss y el ID token. Un returnTo que no sea una ruta de su aplicación se ignora.
5. Proteger páginas con el proxy
proxy.ts (middleware.ts antes de Next.js 16) renueva la sesión antes de que se renderice una página y protege las rutas que usted elija. Una solicitud de página sin sesión se redirige a /auth/login con returnTo; una solicitud a /api/… recibe 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;
}
Las rutas que requieren una sesión están en una sola función:
export function isProtected(pathname: string): boolean {
return pathname === "/dashboard" || pathname.startsWith("/dashboard/") || pathname.startsWith("/api/");
}
El patrón del proxy puede no cubrir una ruta, de modo que cada página y cada controlador de ruta que necesite un usuario también comprueba la sesión por sí mismo.
6. Llamar a su API
En un Server Component, lea la sesión y tome el token de acceso del almacén de cookies. El token permanece en el servidor; el navegador recibe solo el 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 las notificaciones del ID token, como subject, email y name. La sesión nunca contiene un token.
La llamada en sí es un fetch normal con un token de portador:
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 controlador de ruta también puede establecer cookies, así que usa updateSession, que renueva un token de acceso próximo a caducar:
// 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 });
No pase el token de acceso a un Client Component.
7. Cerrar sesión
Cerrar sesión es un formulario que envía una solicitud POST a /auth/logout. El SDK elimina la cookie de sesión, revoca el refresh token y envía el navegador al endpoint de cierre de sesión de su entorno, que lo devuelve a su aplicación. Una solicitud que el navegador marca como procedente de otro sitio se rechaza.
<form action="/auth/logout" method="post">
<button type="submit">Sign out</button>
</form>
8. Ejecutar la aplicación
Inicie la API del inicio rápido de Express (o cualquier API que acepte los tokens) y ejecute la aplicación:
npm run dev
Abra http://localhost:3000, elija Sign in, inicie sesión en KeenAuth y abra el panel.
9. Probar sin red
Las pruebas no necesitan red ni una cuenta de KeenAuth. Un fetch simulado hace de servidor de autorización (descubrimiento y la solicitud de autorización enviada previamente), y Node ejecuta directamente las pruebas en 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
Las pruebas comprueban que la configuración se valida al arrancar, que una página sin sesión redirige al inicio de sesión y que una ruta de API responde 401, que el inicio de sesión envía una solicitud PKCE S256 enviada previamente y establece una cookie HttpOnly sellada, que se rechaza un callback con un state desconocido, que se rechaza un cierre de sesión desde otro sitio y que el token de acceso llega a la API en la cabecera Authorization.
El inicio de sesión completo con un navegador real necesita un entorno de KeenAuth. No forma parte de las pruebas sin conexión.
Detrás de un proxy o balanceador de carga
Defina APP_BASE_URL con la URL pública de la aplicación. El SDK nunca lee Host ni X-Forwarded-* para construir redirecciones. Con varias instancias del servidor, dos de ellas podrían renovar a la vez el mismo refresh token; consulte Límites actuales.
Próximos pasos
- SDK de Next.js: todas las opciones, la cookie de sesión y los límites actuales.
- Proteger una API de Express: la API a la que llama esta aplicación.
- Verificación de tokens: el verificador bajo el middleware de su API.