How does Laravel protect against CSRF attacks

CSRF(cross-site request forgery) is a malicious attack that relies on an authenticated user’s identity to get unauthorized permissions and is now a very common attack.

Laravel has provided us with a mechanism for protecting against CSRF. Now let’s see how it works. The following code is based on Laravel 7.x and there is no big difference with other versions.

Laravel use Http/Middleware/app/Middleware VerifyCsrfToken.php to do CSRF protection, look at the source code:

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [];
}

You’ll find that VerifyCsrfToken class directly inherits Illuminate\Foundation\Http\Middleware\VerifyCsrfToken, but provides the ability to add routes that don’t require CSRF protection (here because validation is not always required in some scenarios).

Let’s check the main code of Illuminate\Foundation\Http\Middleware\VerifyCsrfToken:

/**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     *
     * @throws \Illuminate\Session\TokenMismatchException
     */
    public function handle($request, Closure $next)
    {
        if (
            $this->isReading($request) ||
            $this->runningUnitTests() ||
            $this->inExceptArray($request) ||
            $this->tokensMatch($request)
        ) {
            return tap($next($request), function ($response) use ($request) {
                if ($this->shouldAddXsrfTokenCookie()) {
                    $this->addCookieToResponse($request, $response);
                }
            });
        }

        throw new TokenMismatchException('CSRF token mismatch.');
    }

As we can see, the handle method evaluates the following four conditions:

  1. Is the request a read request such as GET, HEAD;
  2. Is the request for unit-test;
  3. Is it a request that does not need protection(is request in except array);
  4. Is the token verified;

If a request meets any of these condition, Laravel sets a token to the cookie and stores the token in session. The next write request has to send with this token.

Next, let’s look at how tokensMatch method verify the token:

   /**
     * Determine if the session and input CSRF tokens match.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function tokensMatch($request)
    {
        $token = $this->getTokenFromRequest($request);

        return is_string($request->session()->token()) &&
               is_string($token) &&
               hash_equals($request->session()->token(), $token);
    }

    /**
     * Get the CSRF token from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    protected function getTokenFromRequest($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
        }

        return $token;
    }

First, tokensMatch method gets the token value in the request through getTokenFromRequest method, and then compares it with the value in the session to determine whether it is consistent.

The above is how Laravel guards against CSRF attack, any callback is appreciated.

RSS