---
type: how-to
title: Recetas de proxy inverso y el contrato del callback
summary: Cablea la puerta de proxy en Traefik o Caddy, aloja el callback que convierte un pase resuelto en la cookie, y pon puerta a Authelia sin tocarlo.
---

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](/docs/proxy-page-gate/set-up) 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 cabecera `Cookie` del visitante reenviada. `204` significa permitir, `401` significa reto.
- **Reto**: redirige el navegador a `https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<la URL original>`.

## 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).

```yaml
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-app
```

## Caddy

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.

```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 reto 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). Añade esta única ruta a tu origen (o como un location dedicado en el proxy). Debe:

1. Leer `cpt_gate` y `to` desde el **cuerpo** del POST, nunca la URL (un pase en una URL se filtra a logs y referrers).
2. Confirmar que `to` está en tu propio origen, y rechazar cualquier otra cosa, para que el callback no pueda convertirse en un open redirect.
3. Fijar la cookie con los atributos de abajo.
4. Redirigir (`303`) a `to`.

```js
// 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.

## 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/callback` como arriba.

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 proxy](/docs/proxy-page-gate/set-up): el recorrido con nginx y el despliegue con vista previa primero.
- [Panorama](/docs/proxy-page-gate/overview): el concepto y la cookie de paso libre.
- [Estadísticas](/docs/proxy-page-gate/statistics): confirma el cableado observando reto frente a paso libre.
