12
respostas

Laravel - AJAX e CSRF

Olá, estou com um problema fazendo um ajax juntamente com o laravel, onde o request ajax funciona nas 2 primeiras vezes e depois nas seguintes dá o erro: "TokenMismatchException".

AJAX:

var param_array = {
    ....
}

$.ajaxSetup({
   headers: {
      'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
   }
});


$.ajax({
   url: url,
   type: 'POST',
   dataType: 'json',
   data: param_array,
});

HTML

<meta name="csrf-token" content="{{ csrf_token() }}">

É um botão de salvar alterações. As primeiras 2 ou 3 vezes que envio os dados, ao clicar, funcionam bem, porém nas demais retornam o erro citado. Eu já tentei enviando também um parâmetro "_token" mas deu na mesma.

Porque está dando esse problema? Essa chave não funciona com diversos requests?

Imagem http://ap.imagensbrasil.org/images/2017/04/10/Capturar.png

12 respostas

Kelvym, bom dia!

A mesma chave funciona com mais de um request.

Eu tive o mesmo problema, e sinceramente, usando a tag meta e setando headers no ajax, nunca funcionou comigo.

Eu sempre enviei o _token com os dados do formulário.

Ex:

colocando o campo no formulário
<form>
<?php echo csrf_field() ?>
</form>

Ou montando no ajax:

$.ajax({
   url: url,
   type: 'POST',
   dataType: 'json',
   data: {
          "_token" : $('meta[name="csrf-token"]').attr('content'),
         .... demais campos .....
   }
});

Então, eu tentei assim tbm, fiz das duas formas juntas porém ainda não dá certo. =\

Sendo assim, o problema provavelmente não esteja no token, pois esta é a forma correta de usar.

Uma pergunta, as sessões estão funcionando normalmente?

Verifique nos arquivos, ou no banco (dependendo de como você configurou).

O problema que estou procurando, é se cada vez que você atualiza a página, uma sessão nova é criada, ou não?

Se sim, ai está o problema. Quando a sessão é renovada, o token é perdido e a submissão da requisição não funciona.

Até por que, se estiver com este problema, o próprio request cria uma sessão nova, e obviamente o token já é inválido.

Desculpa, mas acho q não entendi. Como assim renovando a sessão? Se renovasse a sessão, ia deslogar do usuário, certo? E é tudo enviado com ajax, então não atualiza a página. Ou to viajando e vc está falando de outra coisa? (ainda estou aprendendo =) )

Sim, renovar a sessão desloga o usuário.

É que tive um problema parecido com o csrf, que no final das contas, descobri que sempre que eu enviara um ajax, o PHP gerava uma sessão nova, ou seja, outro session_id.

Aparentemente o usuário não era deslogado, pois o problema acontecia via ajax apenas, e ao dar F5 na página, eu via que o usuário tinha sido deslogado "sozinho".

Entendeu meu raciocínio?

Estou tentando saber se seu Laravel está dando o mesmo problema com a sessão que eu tive. Pois o csrf depende dos dados de sessão para funcionar, são dados que o Laravel cria.

Acho q entendi, mas eu testei aqui e não deslogou. Há uma forma de fazer um teste de key atual juntamente com o request do ajax pra eu testar se mudou? Mas acho q a key não muda pq não desloga e se eu der f5 e enviar novamente, funciona nas primeiras vezes novamente.

|||||||E o estranho é q isso varia de 2 a diversas vezes que funcionam e depois para. Varia bastante.

||||||E eu estou testando usando o artisan, então está localmente.(não sei se isso pode estar atrapalhando tbm)

Realmente é muito estranho funcionar as vezes 2 chamadas e as vezes várias.

Pensando sobre seu problema aqui, pode não ser a melhor solução de todas, mas acredito que pelo menos até que surja uma forma melhor, você já pensou em gerar um novo token dinamicamente?

Por exemplo, você envia seu request ao servidor e recebe um json como resposta, certo?

E se nesta resposta, você enviar um novo token que será usado na próxima chamada.

Por exemplo, a primeira chamada vai com um token xyz, na resposta você coloca dentre seus campos, outro token. O qual você armazena em uma variável javascript e envia na próxima chamada.

No seu controller, seria algo assim:

$retorno = ["campo1" => "valor", "campo2" => "valor", "novoToken" => csrf_token()];

echo json_encode( $retorno );

Ou qualquer coisa parecida, mas o foco é gerar um novo token para a saída json.

No javascript, você pega esse valor retornado e guarda para a próxima chamada.

Assim podemos saber se o problema está em repetir o mesmo token em todas as requisições.

Caracas! Eu fiz isso, rodei até começar a dar o problema e, sem dar refresh da página, adicionei a url nas exceções de CSRF pra poder retornar algo(senão só retorna o erro). E com isso eu vi que a chave mudou.

Como pode isso? Não to entendendo a lógica de o token mudar depois de algumas interações do sistema com o banco. Isso é normal? Pq eu poderia utilizar esse novo token que retorna, mas isso parece gambiarra kkkk, será que eu errei em alguma coisa e o sistema está se comportando desta forma?

kkkk

Gambiarra, depende do ponto de vista.

Eu prefiro chamar de RTA (Recurso Técnico Alternativo).

Por segurança, o token muda a cada requisição, mas um token antigo ainda pode ser usado pela mesma sessão em requisições futuras.

Mas tem um limite, que não sei dizer exatamente como é definido.

Mas a ideia do CSRF, é evitar que sejam feitas requisições que não sejam legítimas, ou seja, normalmente geradas por scripts.

Depois de algum tempo, um token antigo é removido da sessão. Tornando-se inválido.

Essa forma de pegar um token novo como te falei para fazer, não é uma boa prática, mas para funcionar como você quer, não vejo outra forma.

Pelo menos não se a ideia for manter a segurança da página com CSRF. Se esta segurança não for relevante, você pode adicionar essa rota na lista de páginas ignoradas, e as requisições para ela não exigirão mais o csrf.

O único motivo que penso para você ter esse erro, é que os tokens estão expirando, não sei se por tempo ou por número de requisições.

Se não for isso, não sei outra forma de fazer. Pois se não tem problema na sessão como falamos antes, só pode ser que estão expirando.

kkkk Entendi. Mas isso ajudou demais, eu nem sabia que estava com esse problema de mudança de token.

Vlw mesmo, Adriano!

Eu estava olhando em alguns fóruns e vi diversas vezes algumas pessoas dizerem que isso acontece pq não foi utilizado o middleware "web" nessas rotas, mas como é uma rota com autenticação, eu estou utilizando o middleware "auth". E tem respostas de usuários dizendo pra usar o middleware "web" para sessões, o que eu não entendi direito.

Sinceramente eu nunca cheguei a utilizar o middleware auth para fazer uma autenticação, eu sempre usei o web para tudo.

Basicamente eu autentico o usuário com o método:

Auth::attempt($credenciais);

O middleware auth, só utilizo em algumas rotas, ex:

Route::get('profile', function () {
    // Only authenticated users may enter...
})->middleware('auth');

Apenas para permitir o acesso se o usuário já estiver logado, mas não para fazer o login.

Se eu preciso verificar se o usuário está logado ou não, mas não justifica a proteção de uma rota. Por exemplo, mostrar essa frase ou essa frase dependendo se está logado ou não, eu faço assim, no caso do Blade:

@if( Auth::check() )
    Frase logado
@else
    Frase deslogado
@endif

Eu estou usando o "auth" pra envolver as diversas rotas que são acessadas apenas quando está logado:

Route::group(['middleware' => ['auth']], function () {

    Route::get('/dashboard/logout', 'Auth\LoginController@logout');

    Route::get('/dashboard', 'DashboardController@index');

    Route::get('/dashboard/annotation', 'AnnotationController@index');
    Route::get('/dashboard/annotation/view', 'AnnotationController@view');
    Route::get('/dashboard/annotation/add', 'AnnotationController@add');
    Route::post('/dashboard/annotation/save', 'AnnotationController@save');

    .....e etc

});

Eu estava fazendo isso pra não ter que ficar repetindo código e, sem querer, deixar alguma função restrita com acesso a todos. Não sabia dessas formas de validação q vc escreveu, vou começar a usar tbm e dar uma estudada.