Laravel 5 CSRF global token hidden field for all forms in a page

前端 未结 6 1952
迷失自我
迷失自我 2020-12-01 23:24

I recently migrated to Laravel 5, and now CSRF check is on every post submission. I thought about removing it but I want to follow the best practices, so I\'ll keep it that

相关标签:
6条回答
  • 2020-12-01 23:59

    You need to pass along the header X-XSRF-TOKEN which contains an encrypted version of the csrf-token.

    There are two ways which this can be done that I am aware of. You can encrypt the token and pass it along to the view:

    $xsrfToken = app('Illuminate\Encryption\Encrypter')->encrypt(csrf_token());
    
    return view('some.ajax.form.view')->with('xsrf_token', $xsrfToken);
    

    Or you can grab the token from cookies using JavaScript (Angular makes this easy). In vanilla JS you might do something like this:

    function getCookie(name) {
        var pattern = RegExp(name + "=.[^;]*")
        matched = document.cookie.match(pattern)
        if (matched) {
            var cookie = matched[0].split('=')
            return decodeURIComponent(cookie[1])
        }
        return false
    }
    

    In jQuery you might then do something like this for the ajax request:

    $.ajax({
        // your request
        //
        beforeSend: function(request) {
            return request.setRequestHeader('X-XSRF-TOKEN', getCookie('XSRF-TOKEN'));
        }
    });
    
    0 讨论(0)
  • 2020-12-02 00:08

    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()}}");
            }
        }
    });
    
    0 讨论(0)
  • 2020-12-02 00:10

    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.

    0 讨论(0)
  • 2020-12-02 00:12

    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)));
        }
    
    0 讨论(0)
  • 2020-12-02 00:13

    I think you can do something like this (not tested will update if I get a chance)

    $(document).on('submit', 'form', function(e)
          $(this).append('<input name="_token" value="{{{ Session::token() }}}">);
    });
    

    you actually might want to store token in a variable that you reupdate as it expires.

    The benefit of appending it on submit is if you append elements via ajax I think it'll still work without having to add anything else.

    EDIT: Here's a great article on using Rails UJS with Laravel (which includes this auto CRSF token functionality): https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439

    0 讨论(0)
  • 2020-12-02 00:16

    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.

    0 讨论(0)
提交回复
热议问题