O contrato do cliente tá numa URL que qualquer um adivinha A tarefa parecia simples: o cliente precisa baixar a nota fiscal dele.
Você salvou em , rodou , mandou o link e foi feliz. .
Semanas depois cai a ficha.
Aquele arquivo está aberto na internet.
Sem login, sem nada.
E o nome é sequencial: quem baixou a só precisa de curiosidade e cinco segundos pra tentar a .
E a .
Então você faz a coisa certa: tira do disco público e cria um sistema pra controlar acesso.
Tabela , model, geração de UUID, coluna , controller que valida, e um comando no scheduler pra limpar os vencidos.
Sessenta linhas depois, funciona.
E aí alguém comenta no PR: "por que você não usou uma URL assinada?" O sistema que você não precisava construir Não tem nada de errado tecnicamente.
O problema é o custo: mais uma tabela crescendo pra sempre, mais um comando no scheduler, mais um caminho pra testar.
E você vai manter isso enquanto o projeto existir.
O Laravel resolve o mesmo problema com uma assinatura criptográfica na própria URL.
Sem estado, sem tabela, sem limpeza.
Como uma URL assinada funciona A ideia é bonita de simples: o Laravel monta a URL com os parâmetros que você quer, calcula um hash disso tudo usando a e cola o hash no final.
Quando o request chega, ele recalcula o hash e compara.
Mexeu em qualquer coisa — o ID, a data de expiração, uma vírgula — a assinatura não fecha e o acesso morre com
403.
Ou seja: o "token" é a própria URL.
Não tem o que guardar, porque a validade está embutida nela.
Duas linhas, dois cenários Cenário 1: o arquivo tá no S3 (ou em qualquer disco que gere URL temporária).
Aqui é literalmente uma linha, e o download nem passa pelo seu servidor: Cinco minutos depois o link morre.
O S3 se recusa a servir.
Zero código seu envolvido.
Se você usa disco , isso também funciona desde o Laravel 11 — só precisa avisar no : Cenário 2: você quer uma rota sua, pra registrar o acesso ou aplicar regra.
O confere a assinatura e a expiração antes do seu código rodar.
Se estiver inválido, 403 e pronto — você não escreve um .
Como usar na prática Nota fiscal enviada por e-mail: gere com validade de 7 dias.
O cliente clica direto do e-mail, sem login, e o link não serve pra mais ninguém depois.
Cancelar inscrição: o caso clássico. sem expiração — o cara clica dois meses depois e funciona, mas ninguém consegue descadastrar outra pessoa trocando o ID na URL.
Convite de cadastro: link com validade de 48 horas que já leva o e-mail assinado dentro.
Se o convidado editar o e-mail pra outro, a assinatura quebra.
A pegadinha: assinatura não é autorização Essa é a confusão que vale gravar.
Uma URL assinada garante que ninguém adulterou o link.
Ela não garante que quem está clicando é o dono do arquivo.
Se o link vazar no WhatsApp, quem tiver o link entra.
Pra dado sensível, as duas coisas convivem: Assinatura para quem está fora do login (e-mail, webhook, convite).
Policy para quem está dentro.
Expiração curta em tudo que for confidencial.
E o outro tropeço, o mais comum de todos: com disco é público de verdade.
Não é "público pro usuário logado", é público pro Google.
Se o arquivo tem dono, ele não mora ali — mora no disco privado, e sai por uma rota.
Bônus: dois detalhes que economizam uma tarde Trocar a invalida todas as assinaturas em circulação. É até desejável (é o seu "logout de emergência" de links), mas se você rotacionar a chave num dia de disparo de e-mail, prepare o suporte.
Atrás de proxy, cuide da URL.
Se ou os estiverem tortos, o Laravel calcula a assinatura sobre um host diferente do que o usuário acessou e tudo dá
403.
O sintoma é sempre o mesmo: funciona local, quebra em produção.
Antes de você fechar a aba A lição aqui é maior que URL assinada: antes de criar tabela e comando de limpeza pra resolver algo genérico, dá uma busca na doc. "Expirar um link" é um problema que todo mundo tem — as chances de já vir resolvido são altas.
E o código que você não escreve é o único que nunca tem bug.
Agora fala a verdade: você tem arquivo de clie