Recetas de proxy inverso y el contrato del callback
Todo proxy inverso necesita las mismas dos piezas: una comprobación autorizador que pregunta a Caputchin si una petición lleva un pase válido, y un callback en tu origen que convierte un pase resuelto en la cookie cpt_gate. El recorrido con nginx muestra la forma de extremo a extremo; esta página da los otros proxies, el contrato exacto del callback, y un ejemplo completo de Authelia. Reemplaza YOUR_SITE_KEY con tu clave pública en todo momento.
Los endpoints son los mismos en todas partes:
- Autorizador:
GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY, con la cabeceraCookiedel visitante reenviada.204significa permitir,401significa reto. - Reto: redirige el navegador a
https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<la URL original>.
El autorizador responde solo 204 o 401. Cualquier otra cosa, incluido un timeout, significa que Caputchin es inalcanzable, y lo que pase entonces es tu modo de fallo: bloquear la petición (fallo cerrado, más seguro para un portal de inicio de sesión) o dejarla pasar (fallo abierto). Ponlo en la clave de sitio y el fragmento generado vendrá igual. Dale a la subpetición del autorizador un timeout corto, para que una caída no pueda atascar todas las peticiones mientras decide.
Traefik
Usa un middleware forwardAuth para el autorizador. Traefik reenvía la cabecera Cookie entrante por defecto. Ante un 401 del middleware, redirige el navegador al reto (un middleware de página de error apuntado a una pequeña ruta que emite el 302, o maneja el 401 en tu borde).
http:
middlewares:
cpt-gate:
forwardAuth:
address: "https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY"
# The 401 from forwardAuth is your signal to send the browser to:
# https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<original-url>
routers:
portal:
rule: "Host(`auth.example.com`)"
middlewares: ["cpt-gate"]
service: your-appCaddy
Usa forward_auth para el autorizador y handle_errors para convertir el 401 en una redirección. Esto es un punto de partida; ajusta la sintaxis de las directivas para tu versión de Caddy.
auth.example.com {
forward_auth https://verify.caputchin.com {
uri /v1/gate/authz?site=YOUR_SITE_KEY
copy_headers Cookie
}
handle_errors {
@challenge expression {http.error.status_code} == 401
redir @challenge https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return={scheme}://{host}{uri} 302
}
reverse_proxy your-app:8080
}El callback
La página de verificación alojada termina auto-enviando un formulario POST a https://<tu-origen>/__cpt/callback con dos campos en el cuerpo: cpt_gate (el pase) y to (a dónde enviar al visitante). Si cambiaste la ruta del callback en la clave de sitio, el POST va ahí en su lugar. Añade esta única ruta a tu origen (o como un location dedicado en el proxy). Debe:
- Leer
cpt_gateytodesde el cuerpo del POST, nunca la URL (un pase en una URL se filtra a logs y referrers). - Confirmar que
toestá en tu propio origen, y rechazar cualquier otra cosa, para que el callback no pueda convertirse en un open redirect. - Fijar la cookie con los atributos de abajo.
- Redirigir (
303) ato.
// Any tiny handler on your origin works. Express shown; ~15 lines.
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
const pass = String(req.body.cpt_gate || "");
const to = String(req.body.to || "/");
const target = new URL(to, `https://${req.headers.host}`);
if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
res.setHeader(
"Set-Cookie",
`cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
);
res.redirect(303, target.pathname + target.search);
});Los atributos de la cookie son todo el modelo de seguridad. Como el pase no está atado a un dispositivo, HttpOnly (bloquea el robo por script), Secure (solo HTTPS), SameSite=Lax, y un Max-Age corto (que empareje tu TTL de paso libre) son lo que lo protege. Además: no registres el valor cpt_gate en tus access logs, y mantén Referrer-Policy: no-referrer en el callback para que el pase nunca viaje de acompañante en una cabecera referrer.
Modo de fallo por proxy
Caputchin nunca ve una decisión de modo de fallo, así que nunca aparece en tus estadísticas: tu proxy la toma localmente cuando no puede alcanzar al autorizador.
- nginx:
proxy_intercept_errors on;máserror_page 500 502 503 504 = @cpt_unreachable;, donde ese location con nombre hacereturn 403(cerrado) oreturn 204(abierto). - Traefik: el middleware
forwardAuthda error ante una dirección inalcanzable; enruta eso a un servicio que devuelva403(cerrado), o quita el middleware del router detrás de un healthcheck (abierto). - Caddy: amplía
handle_errorscon un matcher sobre5xxque hagarespond 403(cerrado) orespond 204(abierto).
Alcance por rutas, proxy a proxy
Cubrir solo estas rutas y No cubrir nunca estas rutas dan forma a la configuración generada; tu proxy es quien las hace cumplir.
- nginx: un
location ^~ <prefijo>por cada ruta con puerta, yauth_request off;dentro de cada una excluida. Un patrón de extensión como*.phpse vuelvelocation ~* \.php$. - Traefik: engancha el middleware
cpt-gatesolo a los routers cuyas reglas casen con las rutas con puerta, y déjalo fuera del resto. - Caddy: envuelve
forward_authen un matcher de ruta, por ejemplo@gated path /admin/* /loginy luegoforward_auth @gated ....
Nunca pongas puerta a la subpetición del autorizador ni a tu ruta de callback.
Ejemplo resuelto: Authelia
El portal de inicio de sesión de Authelia es una app de una sola página compilada, sin lugar donde incrustar el widget y sin hook de plugin, así que la puerta es la manera de protegerlo. Authelia ya vive tras un proxy inverso (así funciona su propio ForwardAuth), y ese proxy es donde va la puerta. Authelia en sí se deja completamente sin modificar.
Pon la puerta en el vhost del portal (por ejemplo auth.example.com):
- Pon puerta al HTML del portal y al endpoint de inicio de sesión de primer factor (
/api/firstfactor). - Excluye el endpoint verify del propio ForwardAuth de Authelia (
/api/verify), que el proxy llama en cada petición upstream protegida. Ponerle puerta rompería cada app protegida tras Authelia. - Excluye los health checks y los assets estáticos que la propia página del reto necesita.
- Añade la ruta
/__cpt/callbackcomo arriba (o tu ruta de callback configurada).
Esos cuatro puntos son exactamente lo que expresan los ajustes de alcance de rutas: / y /api/firstfactor como rutas con puerta, /api/verify y tu healthcheck como rutas excluidas.
La puerta mantiene los bots fuera del portal; la Regulation incorporada de Authelia (límite de reintentos y baneo) sigue topando los reintentos de credenciales de quien pase, así que las dos capas se apilan. La puerta protege solo el portal interactivo (la UI de inicio de sesión y consentimiento); no pone puerta, ni debe, a los endpoints de token no interactivos.
Véase también
- Configura la puerta de página en el proxy: el recorrido con nginx y el despliegue con vista previa primero.
- Personaliza el reto: tipo de reto, idioma, tema, nombre de la cookie, ruta del callback, alcance de rutas.
- Panorama: el concepto y la cookie de paso libre.
- Estadísticas: confirma el cableado observando reto frente a paso libre.