Se você já criou um formulário no Laravel, com certeza esbarrou naquela diretiva @csrf que a gente coloca sem nem pensar muito. Mas você já parou pra pensar o que ela realmente faz e por que, se você esquecer dela, o Laravel te devolve um belo de um erro 419 Page Expired? Pois é, tudo isso gira em torno de um ataque chamado CSRF, e é sobre ele que vamos falar.
#O que é CSRF?
CSRF significa Cross-Site Request Forgery, ou "falsificação de requisição entre sites". A ideia do ataque é simples e por isso mesmo perigosa: fazer o navegador da vítima enviar uma requisição para um site no qual ela já está autenticada, sem que ela perceba.
O ponto central que torna esse ataque possível é o comportamento dos cookies no navegador. Quando você faz login em banco.com, o servidor te devolve um cookie de sessão. A partir daí, toda requisição que o seu navegador fizer para banco.com vai levar esse cookie junto, automaticamente. E aqui está o pulo do gato: o navegador não liga de onde a requisição partiu. Se um site totalmente diferente disparar uma requisição para banco.com, o cookie de sessão vai junto do mesmo jeito.
#Como o ataque funciona na prática
Imagina que o seu banco tem um endpoint de transferência que funciona assim:
1<form action="https://banco.com/transferir" method="POST">
2 <input type="text" name="valor">
3 <input type="text" name="destino">
4 <button>Transferir</button>
5</form>
Você está logado no seu banco em uma aba. Em outra aba, você abre um site qualquer que recebeu de um e-mail suspeito. O que você não sabe é que esse site malicioso tem escondido nele algo assim:
1<form action="https://banco.com/transferir" method="POST" id="ataque">
2 <input type="hidden" name="valor" value="10000">
3 <input type="hidden" name="destino" value="conta-do-atacante">
4</form>
5
6<script>
7 document.getElementById('ataque').submit();
8</script>
Quando a página carrega, o JavaScript envia o formulário sozinho. O navegador faz a requisição para banco.com, e como é uma requisição para o domínio do banco, ele anexa o seu cookie de sessão automaticamente. Do ponto de vista do servidor do banco, é uma requisição perfeitamente autenticada, vinda de um usuário logado. E lá se vão os seus dez mil.
O nome "falsificação de requisição entre sites" faz todo sentido: um site (o malicioso) forja uma requisição que parece ter vindo de você para outro site (o banco).
#Qual o objetivo de se proteger contra isso?
O objetivo da proteção CSRF é garantir que uma requisição que altera estado (um POST, PUT, PATCH ou DELETE) tenha realmente partido da sua aplicação, e não de um terceiro se aproveitando da sessão ativa do usuário.
Repara que o problema não é autenticação. A vítima está autenticada de verdade, o cookie é legítimo. O problema é a intenção: como o servidor prova que foi o usuário quem, de fato, quis fazer aquela ação, e não um site externo agindo por baixo dos panos? É exatamente esse buraco que o token CSRF fecha.
#Como o Laravel resolve isso
A estratégia usada pelo Laravel (e pela maioria dos frameworks) é o padrão do token sincronizador. A lógica é a seguinte: para cada sessão de usuário, o Laravel gera um token secreto e aleatório. Esse token fica guardado na sessão, no lado do servidor.
Toda vez que você gera um formulário, precisa embutir esse mesmo token dentro dele. É aí que entra a diretiva @csrf:
1<form method="POST" action="/perfil">
2 @csrf
3 <input type="text" name="nome">
4 <button>Salvar</button>
5</form>
Essa diretiva simplesmente expande para um campo escondido com o token:
1<input type="hidden" name="_token" value="a-string-aleatoria-e-secreta-da-sessao">
Quando o formulário é enviado, o middleware VerifyCsrfToken entra em ação. Ele compara o _token que veio no corpo da requisição com o token guardado na sessão. Se os dois baterem, a requisição segue. Se não baterem, ou se o token não vier, o Laravel barra tudo com aquela resposta 419.
E por que isso funciona? Porque o site malicioso não tem como saber qual é o seu token. Ele está guardado na sua sessão e só é entregue nas páginas que o próprio banco renderiza pra você. Por conta da Same-Origin Policy, o site do atacante não consegue ler o conteúdo das páginas de banco.com para roubar o token. Sem o token correto, a requisição forjada morre no middleware.
#Onde o token é verificado
No Laravel moderno, o middleware VerifyCsrfToken já vem ativado por padrão no grupo web. Você não precisa configurar nada pra ele funcionar, ele simplesmente está lá protegendo suas rotas.
Vale reforçar: a verificação só acontece em métodos que alteram estado (POST, PUT, PATCH, DELETE). Requisições GET não são verificadas, e nem deveriam ser, já que por convenção GET não muda nada no servidor. Essa é justamente a razão de nunca colocarmos ações destrutivas atrás de um simples link GET.
#E quando é uma requisição via JavaScript?
Nem sempre a gente usa formulário tradicional. Quando você faz uma chamada AJAX ou usa Axios, não tem um @csrf no HTML. Nesses casos, o Laravel também disponibiliza o token de outras formas.
Uma opção clássica é colocar o token em uma meta tag no <head>:
1<meta name="csrf-token" content="{{ csrf_token() }}">
E então mandar ele no cabeçalho da requisição:
1const token = document.querySelector('meta[name="csrf-token"]').content;
2
3fetch('/perfil', {
4 method: 'POST',
5 headers: {
6 'X-CSRF-TOKEN': token,
7 'Content-Type': 'application/json',
8 },
9 body: JSON.stringify({ nome: 'Lucien' }),
10});
O middleware sabe procurar o token tanto no campo _token do corpo quanto no cabeçalho X-CSRF-TOKEN. Se você usa Axios com o starter kit do Laravel, boa parte disso já vem configurada automaticamente, inclusive a leitura do cookie XSRF-TOKEN que o framework envia.
#E as APIs? Precisam de token CSRF?
Essa é uma dúvida comum. APIs stateless, autenticadas por token (tipo Sanctum com tokens de acesso ou JWT), não precisam de proteção CSRF. E o motivo é direto: CSRF só é um problema quando a autenticação depende de algo que o navegador envia sozinho, como o cookie de sessão.
Quando a autenticação é feita por um header Authorization: Bearer <token>, o navegador não anexa esse cabeçalho automaticamente. O site malicioso teria que conhecer o token e adicioná-lo na mão, e se ele já tem o token, o CSRF é o menor dos seus problemas. Por isso as rotas do arquivo de API do Laravel ficam fora do middleware web e não passam pela verificação de token.
O cuidado com CSRF vale principalmente para aplicações que usam sessão baseada em cookie, que é o caso da maioria das aplicações web tradicionais com Blade.
#Resumindo
CSRF é um ataque que abusa do fato de o navegador enviar seus cookies de sessão automaticamente, permitindo que um site malicioso forje requisições autenticadas em seu nome. A defesa é o token sincronizador: um segredo que só a sua aplicação conhece e que precisa acompanhar toda requisição que altera estado.
No Laravel isso é praticamente transparente pra gente. Basta lembrar do @csrf nos formulários e mandar o token nos cabeçalhos das requisições via JavaScript, que o middleware VerifyCsrfToken cuida do resto. Da próxima vez que aparecer aquele 419 Page Expired, você já sabe: alguém esqueceu de mandar o token.