web-dev-qa-db-fra.com

Laravel catch TokenMismatchException

L'exception TokenMismatchException peut-elle être interceptée à l'aide de try catch block? Au lieu d’afficher la page de débogage qui affiche l’exception "TokenMismatchException dans la ligne 46 de VerifyCsrfToken.php", je souhaite qu’elle affiche la page actuelle et n’affiche qu’un message d’erreur.

Je n'ai aucun problème avec le CSRF, je veux juste qu'il affiche toujours la page au lieu de la page de débogage.

Pour répliquer (avec firefox): Étapes:

  1. Ouvrir la page ( http://example.com/login )
  2. Effacer les cookies (domaine, chemin d'accès, session). J'utilise le plugin Web Developer Toolbar ici.
  3. Soumettre le formulaire.

Résultats réels: "Oups, on dirait que quelque chose s'est mal passé". Résultats attendus: affichez toujours la page de connexion, puis transmettez une erreur "Incompatibilité de jeton" ou quelque chose du genre.

Notez que lorsque j'ai effacé les cookies, je n'ai pas actualisé la page afin que le jeton génère une nouvelle clé et le force à sortir de l'erreur.

MISE À JOUR (FORMULAIRE AJOUTÉ):

        <form class="form-horizontal" action="<?php echo route($formActionStoreUrl); ?>" method="post">
        <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>" />
        <div class="form-group">
            <label for="txtCode" class="col-sm-1 control-label">Code</label>
            <div class="col-sm-11">
                <input type="text" name="txtCode" id="txtCode" class="form-control" placeholder="Code" />
            </div>
        </div>
        <div class="form-group">
            <label for="txtDesc" class="col-sm-1 control-label">Description</label>
            <div class="col-sm-11">
                <input type="text" name="txtDesc" id="txtDesc" class="form-control" placeholder="Description" />
            </div>
        </div>
        <div class="form-group">
            <label for="cbxInactive" class="col-sm-1 control-label">Inactive</label>
            <div class="col-sm-11">
                <div class="checkbox">
                    <label>
                        <input type="checkbox" name="cbxInactive" id="cbxInactive" value="inactive" />&nbsp;
                        <span class="check"></span>
                    </label>
                </div>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-12">
                <button type="submit" class="btn btn-primary pull-right"><i class="fa fa-save fa-lg"></i> Save</button>
            </div>
        </div>
    </form>

Rien d'extraordinaire ici. Juste une forme ordinaire. Comme ce que j'ai dit, le formulaire fonctionne parfaitement. C’est juste au moment où j’ai énoncé les étapes ci-dessus que le TOKEN a expiré. Ma question est la suivante: le formulaire devrait-il se comporter de cette manière? Je veux dire, chaque fois que je supprime les cookies et la session, je dois également recharger la page? Est-ce ainsi que CSRF fonctionne ici?

48
basagabi

Vous pouvez gérer l’exception TokenMismatchException dans App\Exceptions\Handler.php

<?php namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Session\TokenMismatchException;


class Handler extends ExceptionHandler {


    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        'Symfony\Component\HttpKernel\Exception\HttpException'
    ];
    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $e
     * @return void
     */
    public function report(Exception $e)
    {
        return parent::report($e);
    }
    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        if ($e instanceof TokenMismatchException){
            // Redirect to a form. Here is an example of how I handle mine
            return redirect($request->fullUrl())->with('csrf_error',"Oops! Seems you couldn't submit form for a long time. Please try again.");
        }

        return parent::render($request, $e);
    }
}
89
Emeka Mbah

Une meilleure solution de Laravel 5

dans App\Exceptions\Handler.php
Renvoie l'utilisateur au formulaire avec un nouveau jeton CSRF valide, afin qu'il puisse simplement renvoyer le formulaire sans le remplir à nouveau.

public function render($request, Exception $e)
    {
         if($e instanceof \Illuminate\Session\TokenMismatchException){
              return redirect()
                  ->back()
                  ->withInput($request->except('_token'))
                  ->withMessage('Your explanation message depending on how much you want to dumb it down, lol!');
        }
        return parent::render($request, $e);
    }

J'aime aussi beaucoup cette idée:

https://github.com/GeneaLabs/laravel-caffeine

15
Neo

Au lieu d'essayer d'attraper l'exception, il suffit de rediriger l'utilisateur vers la même page et de lui demander de répéter l'action.

Utilisez ce code dans le fichier App\Http\Middleware\VerifyCsrfToken.php

<?php
namespace App\Http\Middleware;
use Closure;
use Redirect;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
    ];

    public function handle( $request, Closure $next )
    {
        if (
            $this->isReading($request) ||
            $this->runningUnitTests() ||
            $this->shouldPassThrough($request) ||
            $this->tokensMatch($request)
        ) {
            return $this->addCookieToResponse($request, $next($request));
        }

        // redirect the user back to the last page and show error
        return Redirect::back()->withError('Sorry, we could not verify your request. Please try again.');
    }
}
11
Lee

Laravel 5.2: Modifiez App\Exceptions\Handler.php comme ceci:

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

use Illuminate\Session\TokenMismatchException;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $e
     * @return void
     */
    public function report(Exception $e)
    {
        parent::report($e);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        if ($e instanceof TokenMismatchException) {
            abort(400); /* bad request */
        }
        return parent::render($request, $e);
    }
}

Dans AJAX demande que vous puissiez répondre au client en utilisant la fonction abort (), puis gérer la réponse côté client en utilisant AJAX jqXHR.status très facilement, par exemple en affichant un message et en actualisant la page. N'oubliez pas d'attraper le code d'état HTML dans l'événement ajaxComplete de jQuery:

$(document).ajaxComplete(function(event, xhr, settings) {
  switch (xhr.status) {
    case 400:
      status_write('Bad Response!!!', 'error');
      location.reload();
  }
}
3
MDR