ecosystem-wikicómo extender los templates sin romper sus convenciones
‹ todo el contenido

Cómo proteger un panel con él

dev-identity-server
último cambio 2026-09-25

Todo el sentido de este servidor es que haya más de un consumidor. Hoy solo hay uno —el monitor de colas de notification-api— así que esta página describe lo que ese hace, que es la plantilla para el siguiente.

Son cuatro piezas.

1 · El secreto compartido

IDENTITY_JWT_SECRET tiene que valer exactamente lo mismo en los dos servicios. Se llama igual en ambos, así que buscar el nombre encuentra su pareja. Es lo único que necesita el consumidor para verificar una sesión: no hace ninguna llamada de vuelta al identity server.

Eso tiene una consecuencia buena y una mala. La buena: un panel sigue aceptando sesiones ya emitidas aunque el identity server esté caído. La mala: no hay revocación. Un token válido lo es hasta que expira, pasen los 15 minutos.

2 · La puerta

Un middleware delante de la ruta protegida. Si no hay sesión, en vez de devolver 401 redirige al login, diciendo a dónde volver:

const redirectUri = `${env.NOTIFICATIONS_BASE_URL}/admin/auth/callback`;
res.redirect(
  `${identityUrl}/auth/google?redirect_uri=${encodeURIComponent(redirectUri)}`,
);

Ese redirect_uri tiene que estar en la allowlist del identity server, y la coincidencia es exacta: sobra una barra final y el flujo muere con un 400.

3 · El callback: verificar otra vez

El consumidor recibe ?token= y no se fía:

try {
  jwt.verify(token, env.IDENTITY_JWT_SECRET);
} catch {
  return res.status(401).send("Invalid or expired session token");
}

El comentario del código lo llama defensa en profundidad: el token ya trae la firma del identity server, pero nunca se guarda un token sacado de la query sin comprobarlo.

Verificado, se cambia por una cookie propia del consumidor:

res.cookie(ADMIN_SESSION_COOKIE, token, {
  httpOnly: true,
  secure: isProd,
  sameSite: "lax",
  maxAge: 15 * 60 * 1000, // igual que el TTL del access token del identity server
});

Ese maxAge debe coincidir con la vida del access token. Si la cookie durase más, el usuario seguiría pareciendo autenticado con un token ya caducado y cada petición fallaría de forma rara en vez de mandarle a loguearse otra vez.

4 · El respaldo, que no es opcional

notification-api mantiene dos vías de entrada independientes: la sesión de Google y una contraseña por Basic Auth.

if (hasValidAdminSession(req, env) || hasValidBasicAuth(req, env)) return next();

El motivo está escrito en el propio fichero: el identity server es una instancia única y sin alta disponibilidad, y perder el acceso al monitor de colas justo cuando eso está caído sería un incidente en sí mismo.

Al montar un consumidor nuevo, deja un camino alternativo. No es desconfianza del diseño: es que este servicio no se puede replicar, así que su caída es total.

La comparación de la contraseña usa timingSafeEqual, no ===.

Checklist para un consumidor nuevo