Laravel 5 CSRF global token hidden field for all forms in a page
You can use something like this at the bottom of the page:
$('form').append('{{csrf_field()}}');
This will append a hidden input to all your forms
:
<input type="hidden" name="_token" value="yIcHUzipr2Y2McGE3EUk5JwLOPjxrC3yEBetRtlV">
And for all your AJAX requests:
$.ajaxSetup({
beforeSend: function (xhr, settings) {
//////////// Only for your domain
if (settings.url.indexOf(document.domain) >= 0) {
xhr.setRequestHeader("X-CSRF-Token", "{{csrf_token()}}");
}
}
});
Here are some excerpts of how I got my CSRF working for all the different scenarios in my jQuery Mobile application that I recently upgraded to use Laravel 5:
I added an encrypted csrf token in a variable that will be passed to my views in my main base controller:
app\Http\Controllers\MyController.php
$this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token());
Then, I added the meta tag in my main view header:
resources\views\partials\htmlHeader.blade.php
<meta name="_token" content="{!! $encrypted_csrf_token !!}"/>
Then, I also added this jquery snippet as suggested in some forums:
$(function () {
$.ajaxSetup({
headers: {
'X-XSRF-TOKEN': $('meta[name="_token"]').attr('content')
}
});
});
But, the key (for my setup at least) was the addition of the check for the XSRF-TOKEN
cookie in my custom VerifyCsrfToken middleware:
app\Http\Middleware\VerifyCsrfToken.php:
/**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request)
{
$token = $request->session()->token();
$header = $request->header('X-XSRF-TOKEN');
$cookie = $request->cookie('XSRF-TOKEN');
return StringUtils::equals($token, $request->input('_token')) ||
($header && StringUtils::equals($token, $this->encrypter->decrypt($header))) ||
($cookie && StringUtils::equals($token, $cookie));
}
Before I added that, just about all of my AJAX POSTs (including form submissions and lazyloading listviews) were failing due to a TokenMismatchException
.
EDIT: On second thought, I'm not sure how much sense it makes to compare the session token with the one set in the cookie (which would have come from the session token in the first place right?). That may have just been bypassing the security of it all.
I think my main issue was with the jquery snippet above which was supposed to be adding the X-XSRF-TOKEN header to every ajax request. That wasn't working for me in my in jQuery Mobile app (specifically, in my lazyloader plugin) until I added some options for the plugin itself. I added a new default selector csrf
(which would be meta[name="_token"]
in this case) and a new default setting csrfHeaderKey
(which would be X-XSRF-TOKEN
in this case). Basically, during initialization of the plugin, a new _headers
property is initialized with the CSRF token if one is locatable by the csrf
selector (default or user-defined). Then, in the 3 different places where an ajax POST can be fired off (when resetting session variables or when lazyloading a listview) the headers option of $.ajax is set with whatever is in _headers
.
Anyway, since the X-XSRF-TOKEN received on the server-side comes from the encrypted meta _token, I think the CSRF protection is now working as it should.
My app\Http\Middleware\VerifyCsrfToken.php
now looks like this (which is essentially back to the default implementation provided by Laravel 5 - LOL):
/**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request)
{
$token = $request->session()->token();
$_token = $request->input('_token');
$header = $request->header('X-XSRF-TOKEN');
return StringUtils::equals($token, $_token) ||
($header && StringUtils::equals($token, $this->encrypter->decrypt($header)));
}
I don't see any drawbacks. You can easily create a global token field in your layout file:
<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />
Or if you use the form builder:
{!! Form::token() !!}
In jQuery you could use something like this to attach the token to every request.
There is a helper to add the form token inside forms. You can just use
{!! csrf_field() !!}
inside the forms. It will add the hidden input and the token.