Skip to content
A CSRF Anti Forgery Middleware (PSR-15)
PHP
Branch: master
Clone or download
Latest commit aa4f4ee Sep 6, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src Moved path May 5, 2019
tests Moved path May 5, 2019
.cs.php Update build May 5, 2019
.editorconfig Init Dec 10, 2017
.gitattributes Init Dec 10, 2017
.gitignore Changed namespace and project name Mar 8, 2019
.scrutinizer.yml
.travis.yml Change requirements to PHP 7.1 Sep 6, 2019
LICENSE Changed namespace and project name Mar 8, 2019
README.md Update README.md Sep 6, 2019
composer.json Update dependencies Sep 6, 2019
phpcs.xml Update build May 5, 2019
phpstan.neon Init Dec 10, 2017
phpunit.xml

README.md

A CSRF protection middleware

Latest Version on Packagist Software License Build Status Code Coverage Scrutinizer Code Quality Total Downloads

Upgrade Notice: The latest version of this package is a PSR-15 middleware and not compatible with the Slim 3 framework. Please upgrade to Slim 4 or use thephpleague/route.

Important: Since PHP 7.3+ it's possible to send SameSite cookies. This makes CSRF prevention techniques obsolete. Further details can be found here: Sending SameSite cookies in PHP

Requirements

  • PHP 7.1+

Installation

composer require odan/csrf

Slim 4 integration

The following example assumes that thephpleague/container is used as the PSR-11 container.

  1. Step: Register the middleware container entry
<?php

use League\Container\Container;
use Odan\Csrf\CsrfMiddleware;
use Psr\Http\Message\StreamFactoryInterface;
use Slim\Psr7\Factory\ResponseFactory;
use Slim\Psr7\Factory\StreamFactory;

$container = new Container();

// ...

// Register the stream factory container entry
$container->share(StreamFactoryInterface::class, static function () {
    return new StreamFactory();
});

// Register the middleware container entry
$container->share(CsrfMiddleware::class, static function (Container $container) {
    $responseFactory = $container->get(StreamFactoryInterface::class);

    // Start session
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    $sessionId = session_id();

    $csrf = new CsrfMiddleware($responseFactory, $sessionId);

    // Optional: Use the token from another source
    // By default the token will be generated automatically.
    //$csrf->setToken($token);

    return $csrf;
})->addArgument($container);
  1. Step: Add the middleware
<?php

use Odan\Csrf\CsrfMiddleware;
use Slim\Factory\AppFactory;

$app = AppFactory::create();

// ...

$app->add(CsrfMiddleware::class);

thephpleague/route integration

thephpleague/route is a fast PSR-7 based routing and dispatch component including PSR-15 middleware, built on top of FastRoute.

The following example assumes that thephpleague/container is used as the PSR-11 container and nyholm/psr7 as the PSR-7/17 factory implementation:

<?php

use League\Container\Container;
use League\Route\Router;
use Nyholm\Psr7\Factory\Psr17Factory;
use Odan\Csrf\CsrfMiddleware;

$container = new Container();

// Register the container factory
$container->share(CsrfMiddleware::class, function () {
    return new CsrfMiddleware(new Psr17Factory(), session_id());
});

$router = new Router();
$router->setContainer($container));

// Add the middleware to the routes and route groups you want to protect
$router->post('/contact', \App\Action\ContactSubmitAction::class)
    ->middleware(CsrfMiddleware::class);

Using the Aura.Session token

If you are already using the Aura.Session library you can use their Session-ID and CSRF token.

<?php

use Aura\Session\Session;
use League\Container\Container;
use Nyholm\Psr7\Factory\Psr17Factory;
use Odan\Csrf\CsrfMiddleware;

// ...

$container->share(CsrfMiddleware::class, function (Container $container) {
    $session = $container->get(Session::class);
    $token = $session->getCsrfToken()->getValue();
    $csrf = new CsrfMiddleware(new Psr17Factory(), $session->getId());
    
    // Use the token from the aura session object
    $csrf->setToken($token);
    
    return $csrf;
})->addArgument($container);

Options

For security reasons all security related settings are enabled by default.

// Set a secret password to increase the security of the token
$csrf->setSalt('secret');

// Change the name of the hidden input field
$csrf->setTokenName('__token');

// Enable jQuery ajax protection against CSRF attacks
$csrf->protectJqueryAjax(true);

// Enable form protection against CSRF attacks
$csrf->protectForms(true);

// Enable form protection against CSRF attacks, but disable GET forms from protection
$csrf->protectForms(true, false);

Rendering the CSRF field in Twig

Sometimes you want a variable to be accessible to all the templates you use. This is possible inside your config/container.php file:

<?php

use League\Container\Container;
use Twig\Environment as Twig;
use Twig\Loader\FilesystemLoader;
use Odan\Csrf\CsrfMiddleware;

//...

$container->share(Twig::class, function (Container $container) {
    $loader = new FilesystemLoader('templates);
    $twig = new Twig($loader);

    // Add CSRF token as global template variable
    $csrfToken = $container->get(CsrfMiddleware::class)->getToken();
    $twig->addGlobal('csrf_token', $csrfToken);

    return $twig;
})->addArgument($container);

Now, the variable csrf_token is available in all Twig templates:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta name="csrf_token" content="{{ csrf_token }}">
    </head>
    <body>
    </body>
</html>

Known issues

CSRF middleware failed. SessionId not found!

Make sure that the PHP session is already started before invoking the CSRF middleware.

What attack does anti-forgery prevent?

Cross-site request forgery (also known as XSRF or CSRF, pronounced see-surf) is an attack against web-hosted applications whereby a malicious web site can influence the interaction between a client browser and a web site that trusts that browser. These attacks are made possible because web browsers send some types of authentication tokens automatically with every request to a web site. This form of exploit is also known as a one-click attack or as session riding, because the attack takes advantage of the user's previously authenticated session.

An example of a CSRF attack:

  1. A user logs into www.example.com, using forms authentication.
  2. The server authenticates the user and issues a response that includes an authentication cookie.
  3. The user visits a malicious site.
  4. The malicious site contains an HTML form similar to the following:
<h1>You Are a Winner!</h1>
<form action="http://example.com/api/account" method="post">
    <input type="hidden" name="Transaction" value="withdraw" />
    <input type="hidden" name="Amount" value="1000000" />
    <input type="submit" value="Click Me"/>
</form>

Notice that the form action posts to the vulnerable site, not to the malicious site. This is the “cross-site” part of CSRF.

The user clicks the submit button. The browser includes the authentication cookie with the request. The request runs on the server with the user’s authentication context, and can do anything that an authenticated user is allowed to do.

So basically, when example.com receives the CSRF attack it should match the CSRF token in the cookie against the one in the post data, http header or meta tag. A legit request will include both, however, a forgery attack will only include the CSRF token specified in the cookie.

Session and authentication does not protect against CSRF attack. Developers must implement CSRF protection by themselves.

Using SSL does not prevent a CSRF attack, the malicious site can send an https:// request.

Note: This library does not protect your applications that change state with GET requests and is therefore vulnerable to such malicious attacks.

Users can guard against CSRF vulnerabilities by:

  • Logging off of web sites when they have finished using them.
  • Clearing their browser's cookies periodically.

However, CSRF vulnerabilities are fundamentally a problem with the web app, not the end user.

More informations:

How does odan/csrf address CSRF?

HTML forms

The middleware injects anti-forgery tokens for HTML form elements.

For example, the following HTML file will automatically generate anti-forgery tokens:

<form method="post">
  <!-- form markup -->
</form>

Result:

<form method="post">
   <input type="hidden" name="__token" value="the-csrf-token">
  <!-- form markup -->
</form>

You can disable automatic generation of anti-forgery tokens for HTML form elements by:

$csrf->protectForms(false);

JavaScript, AJAX, and SPAs

In traditional HTML-based applications, antiforgery tokens are passed to the server using hidden form fields. In modern JavaScript-based apps and single page applications (SPAs), many requests are made programmatically. These AJAX requests may use other techniques (such as request headers or cookies) to send the token. If cookies are used to store authentication tokens and to authenticate API requests on the server, then CSRF will be a potential problem. However, if local storage is used to store the token, CSRF vulnerability may be mitigated, since values from local storage are not sent automatically to the server with every new request. Thus, using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.

jQuery

The middleware injects a small piece of JavaScript into your html template to protect all jQuery Ajax request against CSRS attacks.

The default header name is: X-CSRF-TOKEN (compatible with Angular)

For example, the following HTML file will automatically generate anti-forgery tokens:

<!DOCTYPE html>
    <head>
       <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    </head>
    <body>
    </body>
</html>

Result:

<!DOCTYPE html>
    <head>
       <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    </head>
    <body>
    <script>$.ajaxSetup({beforeSend: function (xhr) { xhr.setRequestHeader("X-CSRF-Token","the-csrf-token"); }});</script>
    </body>
</html>

You can disable automatic generation of anti-forgery tokens for HTML documents by calling:

$csrf->protectjQueryAjax(false);
You can’t perform that action at this time.