Parece que sua objeção em deixar a sessão aberta enquanto o navegador estiver aberto é a questão dos ataques automatizados. Infelizmente, atualizar o token em cada carregamento de página apenas desencoraja os invasores mais amadores.
Primeiro, suponho que estamos falando de ataques direcionados especificamente ao seu site. (Se estamos falando sobre os bots que vagam por aí e enviam vários formulários, não apenas isso não os impediria, mas há maneiras muito melhores e mais fáceis de fazer isso.) Se for esse o caso, e estou direcionando meus site, aqui está o que meu bot faria:
- Carregar página do formulário.
- Ler token na página do formulário.
- Envie uma solicitação automatizada com esse token.
- Vá para a etapa 1.
(Ou, se eu investigasse seu sistema o suficiente, perceberia que, se incluísse o cabeçalho "isto é AJAX" em cada solicitação, poderia manter um token para sempre. Ou perceberia que o token é meu ID de sessão e enviar meu próprio
PHPSESSID
bolacha.) Este método de alterar o token em cada carregamento de página não faria absolutamente nada para impedir alguém que realmente queria para atacá-lo tão mal. Portanto, como o token não tem efeito na automação, concentre-se em seus efeitos no CSRF.
Da perspectiva de bloquear CSRF, criar um token e mantê-lo até que o usuário feche o navegador parece cumprir todos os objetivos. Ataques CSRF simples são derrotados e o usuário pode abrir várias guias.
TL;DR:atualizar o token uma vez em cada solicitação não aumenta a segurança. Opte pela usabilidade e faça um token por sessão.
No entanto! Se você está extremamente preocupado com envios de formulários duplicados, acidentais ou não, esse problema ainda pode ser resolvido facilmente. A resposta é simples:use dois tokens para dois trabalhos diferentes.
O primeiro token permanecerá o mesmo até que a sessão do navegador termine. Este token existe para evitar ataques CSRF. Qualquer envio deste usuário com este token será aceito.
O segundo token será gerado exclusivamente para cada formulário carregado e será armazenado em uma lista nos dados de sessão do usuário de tokens de formulário aberto. Este token é único e é invalidado assim que é usado. Os envios deste usuário com este token serão aceitos uma vez e apenas uma vez.
Dessa forma, se eu abrir uma guia para o Formulário A e uma guia para o Formulário B, cada um terá meu token anti-CSRF pessoal (CSRF atendido) e meu token de formulário único (resubmissão do formulário atendida). Ambos os problemas são resolvidos sem nenhum efeito negativo na experiência do usuário.
Claro, você pode decidir que isso é demais para implementar para um recurso tão simples. Eu acho que é, de qualquer maneira. Independentemente disso, existe uma solução sólida, se você quiser.