cloudflareaccesszero-trustjwtsegurancadevops

Como funciona o Cloudflare Access: admin protegido sem tela de login própria

Douglas Haruo 10 min 08/09/2026

O Cloudflare Access coloca uma etapa de identidade na frente de um hostname, na borda da Cloudflare, antes de a requisição tocar o seu servidor. Quem abre admin.seudominio.com encontra um login com provedor de identidade (Google, GitHub, SSO corporativo), o serviço que já guarda as contas do time. A alternativa é um PIN de uso único por e-mail. O aplicativo atrás não tem tela de login, banco de usuários nem senha para vazar. Depois de autenticar, cada requisição chega ao aplicativo carregando um JWT, um token assinado pela Cloudflare, no header Cf-Access-Jwt-Assertion. O aplicativo valida esse token e sabe, com assinatura criptográfica, quem está do outro lado. É login, 2FA e SSO como serviço da borda. A metade que quase nenhum tutorial conta é que o aplicativo precisa validar o token. Sem isso, a proteção é menor do que parece.

Este post é a continuação natural da anatomia da VPS que serve tudo por Cloudflare Tunnel sem porta aberta. Lá o assunto era como o tráfego entra; aqui, como as interfaces administrativas dessa mesma caixa autenticam. A matéria-prima são os dois usos reais da casa. O primeiro é o n8n, um app de terceiros, em n8n.haruo.dev. O segundo é o painel de operação do serviço de agentes multi-tenant, um app próprio em painel.haruo.dev que decidiu não ter login. Nomenclatura e comportamento conferidos na documentação oficial em 25/08/2026.


O que o Cloudflare Access resolve?

Toda interface administrativa exposta na internet precisa da mesma lista: senha bem guardada, 2FA, sessão, recuperação de conta, proteção contra força bruta. Escrever isso para um painel interno é custo sem diferencial. Para um app de terceiros, como um n8n ou um Grafana, nem é opção: não dá para enxertar o seu SSO no login dos outros.

O Access move a lista inteira para a borda. A pergunta “quem é você?” é respondida antes de a requisição chegar ao túnel, com o provedor de identidade que você já usa. O aplicativo atrás recebe a resposta pronta, assinada. Os dois usos da casa ilustram os dois casos:

  • App de terceiros (n8n): o Access fica na frente do hostname e pronto. O login do próprio n8n continua existindo por baixo, mas ninguém alcança a tela dele sem antes provar identidade na borda. Duas camadas, zero código.
  • App próprio (painel de operação): o desenho foi mais radical. O painel não tem sistema de login: nenhuma senha, nenhum cookie de sessão próprio, nenhuma tabela de usuários. A identidade da borda é a autenticação, e o serviço valida o token em cada requisição.

O ganho, dito com modéstia: isso reduz a superfície. A página de login, o credential stuffing (o teste em massa de senhas vazadas) e o esqueci-minha-senha deixam de ser problema seu. Não torna nada invulnerável. A garantia depende de duas disciplinas, assunto das próximas seções.

Como funciona o Cloudflare Access, passo a passo?

O mecanismo inteiro, na ordem em que uma requisição o percorre:

  1. O hostname entra pela borda da Cloudflare. No desenho da casa, isso acontece via Cloudflare Tunnel: a origem não tem porta aberta, e o conector abre a conexão de dentro para fora.
  2. Uma Access Application (tipo self-hosted) é declarada para o hostname no painel Zero Trust, a área da Cloudflare onde nenhuma requisição é confiável por padrão. É ela que diz “este endereço exige identidade”. Ela também carrega o Application Audience (AUD) Tag, o identificador que depois aparece no token.
  3. Políticas decidem quem entra. Uma política tem uma ação (Allow, Block, Bypass, Service Auth) e regras que combinam critérios. Include funciona como OU, Require como E, Exclude como NÃO (documentação de políticas, conferida em 25/08/2026). O caso típico de admin de time pequeno: uma política Allow cujo Include é a lista de e-mails autorizados.
  4. O login acontece na borda. Pode ser o provedor de identidade configurado no seu time (Google, GitHub, SAML/OIDC corporativo) ou um one-time PIN por e-mail. O PIN dá 2FA de fato sem nenhum IdP configurado.
  5. A borda emite um JWT assinado (RS256) para o time (<team>.cloudflareaccess.com). No navegador ele vira o cookie CF_Authorization. Em cada requisição encaminhada à origem ele vai no header Cf-Access-Jwt-Assertion.
  6. O aplicativo valida o token. As chaves públicas ficam no JWKS do time, o endereço que publica essas chaves: https://<team>.cloudflareaccess.com/cdn-cgi/access/certs. O app confere assinatura, aud (o AUD Tag da aplicação), iss (https://<team>.cloudflareaccess.com) e validade (guia de validação, conferido em 25/08/2026).

Os passos 1 a 5 são configuração: meia hora de painel, nenhuma linha de código. O passo 6 é o único que é seu, e é o que separa o desenho robusto do teatro.

O erro comum: por que validar o JWT no aplicativo?

A tentação é parar no passo 5: “se a requisição chegou até aqui, o Access deixou passar”. Esse raciocínio confia no roteamento, e roteamento não é criptografia. Ele quebra por dois caminhos concretos:

  • A origem pode ser alcançável por outra rota. Outro container na mesma rede interna, uma porta publicada por engano num redeploy, uma regra de ingress que sobrou. Qualquer requisição que chegue à porta do app sem passar pela borda chega sem Access nenhum. Um app que não valida o token atende essa requisição como se fosse legítima.
  • O header de conveniência não é assinado. Junto com o JWT, o Access manda um header legível com o e-mail autenticado (Cf-Access-Authenticated-User-Email). É cômodo, e é só texto: quem alcança a porta diretamente escreve nele o e-mail que quiser. A própria documentação da Cloudflare avisa: valide o token com a chave pública, para garantir que a requisição veio do Access e não de um terceiro.

O padrão que o painel da casa segue, e que vale para qualquer stack:

  • Verificar a assinatura contra o JWKS do time (RS256), e conferir aud, iss e expiração. Decodificar o payload não basta.
  • Tirar a identidade de dentro do token assinado (claim email), nunca do header de conveniência. Header sem assinatura é decoração.
  • Buscar as chaves do endpoint, com cache e tolerância a rotação. A documentação recomenda não fixar a chave no código, porque a Cloudflare rotaciona as chaves. O desenho certo é um cache com validade curta mais um refresh forçado quando aparece um kid desconhecido, o identificador da chave que assinou o token. Rotação de chave não pode virar 403 para todo mundo até alguém reiniciar o serviço.
  • Sem dependência nova. A verificação de um RS256 cabe em ~40 linhas sobre a biblioteca de criptografia que o serviço provavelmente já tem. Uma dependência inteira de JWT para um único verify() é imposto de manutenção.

Vale dizer também o contrário: validar o JWT não substitui fechar a rota direta. As duas disciplinas somam: a rede interna como fronteira, e o token como prova de que a requisição atravessou a borda autenticada.

Por que falhar fechado importa?

A validação do passo 6 depende de configuração: o domínio do time e o AUD Tag. E toda configuração falta um dia: no primeiro deploy, num restore, num ambiente novo. O que o aplicativo faz nesse momento define a segurança real do desenho:

  • O painel da casa responde 503 com instrução quando as variáveis do Access não existem. A mensagem diz o que configurar e onde. Ele nunca abre “temporariamente sem auth”. Um admin que degrada para aberto é pior que um admin fora do ar, porque fora do ar alguém percebe.
  • O atalho de desenvolvimento é recusado em produção. Existe um modo dev que dispensa o Access para rodar local. O serviço o recusa quando detecta que está sobre o banco de produção. Atalho que só deveria existir em dev tem o hábito de sobreviver a um restore.
  • O hostname do painel só responde as rotas do painel. O mesmo processo serve webhooks e endpoints internos por outros caminhos. Uma guarda de host garante que, chegando pelo hostname público, só /painel* e o healthcheck existem. Access Application na borda e guarda de host na origem: camadas, não alternativas.

O princípio que resume os três pontos: o estado degradado de um admin é desligado, nunca aberto.

Autenticação não é autorização

Passar pelo Access prova quem você é. Não prova que você tem o que ver ali dentro. No painel multi-cliente da casa, o e-mail autenticado (lido do token assinado) é mapeado para um papel num arquivo de dados versionado. Operador vê todos os clientes, cliente vê só os seus. E-mail que passou pela borda mas não está no mapa recebe 403 explicando onde o acesso é concedido. Arquivo ausente significa que ninguém entra: o mesmo princípio de falhar fechado, na camada de autorização.

A fronteira é limpa: a Cloudflare responde “quem é”, o aplicativo responde “pode ver o quê”. Misturar as duas, usando a política do Access como autorização fina, funciona até a primeira exceção por recurso. Aí a política vira um emaranhado que ninguém audita.

Quando NÃO usar o Cloudflare Access?

O Access é ferramenta de admin e time, não de produto. Os limites que o desenho da casa respeita:

  • Login de clientes do seu produto: não. Access autentica membros do seu time Zero Trust, com a experiência de login do time. Os usuários de um SaaS precisam de autenticação do produto (própria ou de um provedor de identidade de produto), com o fluxo, a marca e a escala do produto.
  • Máquina chamando API: em geral, não. Existe Service Auth com service tokens, credenciais para programas em vez de pessoas. Mas se o consumidor é um programa de terceiros, um esquema de API key seu é mais simples e não acopla seus clientes ao seu provedor de borda.
  • Autorização fina como problema central: o Access não resolve. Ele decide por aplicação, não por registro. Se a sua questão é “quem edita este recurso”, você vai escrever autorização de qualquer jeito. O Access só tira da sua mesa o login.
  • Sem disciplina de origem fechada, o ganho encolhe. Digamos que o app continua alcançável direto (porta aberta, IP da origem exposto) e não valida o token. O Access vira um cadeado na porta da frente de uma casa sem parede dos fundos. Nesse cenário, resolva a exposição primeiro.

E um trade-off dito em voz alta: a porta de entrada do seu admin passa a depender de um fornecedor. Para a operação da casa, que já entrega o tráfego pela mesma borda, o acoplamento marginal é zero. Para quem não está nesse desenho, é uma decisão a tomar de olhos abertos.

Quanto custa o Cloudflare Access?

Preço público, conferido em 25/08/2026 na página do produto: o plano gratuito do Zero Trust cobre até 50 usuários por US$ 0. O plano pago (pay-as-you-go) custa US$ 7 por usuário/mês com cobrança anual. O caso deste post é um punhado de pessoas atrás de dois hostnames de admin. Para ele, o plano gratuito cobre com folga, e é o que a casa usa.

O resumo que você leva

  1. Access = login, 2FA e SSO na borda: uma Access Application na frente do hostname, políticas Allow/Include decidindo quem entra, e nenhuma tela de login para escrever. Funciona igual para app de terceiros e app próprio.
  2. O artefato que chega ao seu app é um JWT assinado no header Cf-Access-Jwt-Assertion, com chaves públicas no JWKS do time e claims aud, iss e exp para conferir.
  3. Valide o token; não confie no roteamento. A origem pode ser alcançável por outra rota, e o header com o e-mail não é assinado. Identidade sai de dentro do JWT, sempre.
  4. Busque as chaves do endpoint, com cache e refresh no kid desconhecido. Rotação de chave não pode derrubar o acesso de todo mundo.
  5. Falhe fechado: sem configuração do Access, o admin responde 503 com instrução e nunca abre sem auth. Atalhos de dev são recusados em produção.
  6. Autenticação não é autorização: a borda prova quem é; o app decide quem vê o quê, num mapa de dados que, ausente, nega tudo.
  7. Não é para login de produto nem para autorização fina. É para o seu time alcançar os seus admins. Até 50 usuários, o plano gratuito resolve (preço de 25/08/2026).

Infraestrutura que escala sem quebrar o orçamento

Conta de cloud fora de controle? Opero a minha própria numa VPS única, sem porta aberta, com deploy automático e rollback por healthcheck. O desenho inteiro está publicado aqui.

Ver os posts de infraestrutura →