Caputchin
Porta de proxy

Receitas de proxy reverso e o contrato do callback

Ver como Markdown

Todo proxy reverso precisa das mesmas duas peças: uma checagem autorizador que pergunta ao Caputchin se uma requisição leva um passe válido, e um callback na sua origem que transforma um passe resolvido no cookie cpt_gate. O passo a passo do nginx mostra a forma de ponta a ponta; esta página dá os outros proxies, o contrato exato do callback, e um exemplo completo do Authelia. Substitua YOUR_SITE_KEY pela sua chave pública em toda parte.

Os endpoints são os mesmos em toda parte:

  • Autorizador: GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY, com o cabeçalho Cookie do visitante encaminhado. 204 significa permitir, 401 significa desafio.
  • Desafio: redirecione o navegador para https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<a URL original>.

Traefik

Use um middleware forwardAuth para o autorizador. O Traefik encaminha o cabeçalho Cookie que chega por padrão. Em um 401 do middleware, redirecione o navegador para o desafio (um middleware de página de erro apontado para uma pequena rota que emite o 302, ou trate o 401 na sua edge).

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

Use forward_auth para o autorizador e handle_errors para transformar o 401 em um redirecionamento. Isto é um ponto de partida; ajuste a sintaxe das diretivas para a sua versão do 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
}

O callback

A página de desafio hospedada termina auto-enviando um formulário POST para https://<sua-origem>/__cpt/callback com dois campos no corpo: cpt_gate (o passe) e to (para onde enviar o visitante). Adicione esta única rota à sua origem (ou como uma location dedicada no proxy). Ela deve:

  1. Ler cpt_gate e to do corpo do POST, nunca da URL (um passe em uma URL vaza para logs e referenciadores).
  2. Confirmar que to está na sua própria origem, e rejeitar qualquer outra coisa, para que o callback não possa ser transformado em um open redirect.
  3. Definir o cookie com os atributos abaixo.
  4. Redirecionar (303) para to.
// 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);
});

Os atributos do cookie são todo o modelo de segurança. Como o passe não está amarrado a um dispositivo, HttpOnly (bloqueia o roubo por script), Secure (só HTTPS), SameSite=Lax, e um Max-Age curto (a casar com a sua TTL de liberação) são o que o protege. Além disso: não registre o valor cpt_gate nos seus access logs, e mantenha Referrer-Policy: no-referrer no callback para que o passe nunca viaje de carona em um cabeçalho referenciador.

Exemplo trabalhado: Authelia

O portal de login do Authelia é uma app mono-página compilada sem lugar para incrustar o widget e sem hook de plugin, então a porta é o jeito de protegê-lo. O Authelia já fica atrás de um proxy reverso (é assim que o próprio ForwardAuth dele funciona), e esse proxy é onde a porta vai. O Authelia em si fica completamente sem modificação.

Ponha a porta no vhost do portal (por exemplo auth.example.com):

  • Ponha porta no HTML do portal e no endpoint de login de primeiro fator (/api/firstfactor).
  • Exclua o endpoint verify do próprio ForwardAuth do Authelia (/api/verify), que o proxy chama para toda requisição upstream protegida. Pôr porta nele quebraria toda app protegida atrás do Authelia.
  • Exclua os health checks e os assets estáticos de que a própria página do desafio precisa.
  • Adicione a rota /__cpt/callback como acima.

A porta mantém os bots fora do portal; a Regulation embutida do Authelia (limite de retentativas e banimento) ainda limita as retentativas de credenciais de quem passa, então as duas camadas se empilham. A porta protege só o portal interativo (a UI de login e consentimento); ela não põe porta, e não deve, nos endpoints de token não interativos.

Veja também

Nesta página