web-dev-qa-db-fra.com

Champ masqué de jeton global Laravel 5 CSRF pour tous les formulaires d'une page

J'ai récemment migré vers Laravel 5, et maintenant, le contrôle de la CSRF est appliqué à chaque soumission. Je pensais l'enlever mais je veux suivre les meilleures pratiques, donc je vais continuer comme ça.

Par contre, je ne parviens pas à soumettre des demandes ajax. Ma page comporte plusieurs formulaires et certaines soumissions ne proviennent même pas de formulaires, mais simplement d'appels ajax. Mon idée est d'avoir un seul "jeton" caché sur la page et de le joindre à chaque soumission. Y a-t-il des inconvénients à avoir cette entrée universelle à jeton unique?

Aussi, comment puis-je sortir le jeton? Serait-il possible de simplement créer une entrée masquée sur le pied de page?

18
raphadko

Je ne vois aucun inconvénient. Vous pouvez facilement créer un champ de jeton global dans votre fichier de mise en page:

<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />

Ou si vous utilisez le générateur de formulaire:

{!! Form::token() !!}

Dans jQuery, vous pouvez utiliser quelque chose comme this pour attacher le jeton à chaque requête.

24
lukasgeiter

Il y a un helper pour ajouter le jeton de formulaire à l'intérieur des formulaires. Vous pouvez simplement utiliser

{!! csrf_field() !!}

à l'intérieur des formes. Cela ajoutera l'entrée cachée et le jeton.

20
Arda

Vous pouvez utiliser quelque chose comme ceci au bas de la page:

$('form').append('{{csrf_field()}}');

Ceci ajoutera une entrée cachée à tous vos forms:

<input type="hidden" name="_token" value="yIcHUzipr2Y2McGE3EUk5JwLOPjxrC3yEBetRtlV">

Et pour toutes vos demandes AJAX:

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        //////////// Only for your domain
        if (settings.url.indexOf(document.domain) >= 0) {
            xhr.setRequestHeader("X-CSRF-Token", "{{csrf_token()}}");
        }
    }
});
5
tomloprod

Voici quelques extraits de la manière dont mon CSRF fonctionne pour tous les scénarios de mon application jQuery Mobile que j'ai récemment mise à niveau pour utiliser Laravel 5:

J'ai ajouté un jeton CSRF chiffré dans une variable qui sera transmise à mes vues dans mon contrôleur de base principal: app\Http\Controllers\MyController.php

$this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token());

Ensuite, j'ai ajouté la balise Meta dans l'en-tête de ma vue principale: resources\views\partials\htmlHeader.blade.php

<meta name="_token" content="{!! $encrypted_csrf_token !!}"/>

Ensuite, j'ai également ajouté cet extrait de code jQuery, comme suggéré dans certains forums:

    $(function () {
            $.ajaxSetup({
                    headers: {
                            'X-XSRF-TOKEN': $('meta[name="_token"]').attr('content')
                    }
            });
    });

Mais la clé (pour ma configuration au moins) était l'ajout de la vérification du cookie XSRF-TOKEN dans mon middleware personnalisé VerifyCsrfToken: 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));
    }

Avant d’ajouter cela, à peu près tous mes POST AJAX (y compris les soumissions de formulaires et les consultations de liste de lazyloading) échouaient à cause d'une TokenMismatchException

EDIT: .__ À la réflexion, je ne suis pas sûr de la pertinence de comparer le jeton de session à celui défini dans le cookie (ce qui serait venu du jeton de session en premier lieu, non? ). Cela vient peut-être de contourner la sécurité de tout cela.

Je pense que mon problème principal était avec l'extrait de code jQuery ci-dessus, qui était supposé ajouter l'en-tête X-XSRF-TOKEN à chaque demande ajax. Cela ne fonctionnait pas pour moi dans mon application jQuery Mobile (plus précisément dans mon plugin lazyloader ) jusqu'à ce que j'ajoute des options pour le plugin lui-même. J'ai ajouté un nouveau sélecteur par défaut csrf (qui serait meta[name="_token"] dans ce cas) et un nouveau paramètre par défaut csrfHeaderKey (qui serait X-XSRF-TOKEN dans ce cas). Lors de l’initialisation du plug-in, une nouvelle propriété _headers est initialisée avec le jeton CSRF si celle-ci est localisable par le sélecteur csrf (par défaut ou défini par l'utilisateur). Ensuite, dans les 3 endroits différents où un ajax POST peut être déclenché (lors de la réinitialisation de variables de session ou du chargement d'une liste de vues), l'option d'en-tête de $ .ajax est définie avec tout ce qui est dans _headers

Quoi qu'il en soit, comme le X-XSRF-TOKEN reçu sur le serveur provient de la méta _token cryptée, je pense que la protection CSRF fonctionne maintenant comme il se doit. 

Mon app\Http\Middleware\VerifyCsrfToken.php ressemble maintenant à ceci (ce qui revient essentiellement à l'implémentation par défaut fournie par 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)));
    }
3
dcarrith

Je pense que vous pouvez faire quelque chose comme ça (non testé sera mis à jour si j'en ai l'occasion)

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

vous voudrez peut-être stocker le jeton dans une variable que vous aurez mise à jour lorsqu’elle expirera. 

L'avantage de l'ajouter à submit est que si vous ajoutez des éléments via ajax, je pense que cela fonctionnera toujours sans rien ajouter d'autre.

EDIT: Voici un excellent article sur l'utilisation de Rails UJS avec Laravel (qui inclut cette fonctionnalité jeton CRSF automatique): https://medium.com/@barryvdh/unobtrusive-javascript-with -jquery-ujs-and-laravel-e05f444d3439

2
Sabrina Leggett

Vous devez transmettre l'en-tête X-XSRF-TOKEN qui contient une version chiffrée du csrf-token.

Je sais que cela peut être fait de deux manières. Vous pouvez chiffrer le jeton et le transmettre à la vue:

$xsrfToken = app('Illuminate\Encryption\Encrypter')->encrypt(csrf_token());

return view('some.ajax.form.view')->with('xsrf_token', $xsrfToken);

Ou vous pouvez récupérer le jeton dans les cookies en utilisant JavaScript (Angular facilite cette tâche). Dans Vanilla JS, vous pourriez faire quelque chose comme ceci:

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
}

Dans jQuery, vous pouvez alors faire quelque chose comme ceci pour la requête ajax:

$.ajax({
    // your request
    //
    beforeSend: function(request) {
        return request.setRequestHeader('X-XSRF-TOKEN', getCookie('XSRF-TOKEN'));
    }
});
1
tplaner