Csrf protection based on double submit pattern, cookie - JWT/Branca alternative.
It is based on PSR-7 JWT Authentication Middleware from Tuupola. This middleware is designed to work with JWT/Branca Authentication method and can be used with any framework using PSR-7 or PSR-15 style middlewares (since v1.1.0). It has been tested with Slim Framework.
This middleware does not provide ways to generate Branca/JWT token. However you can find all you needs for generate token with links bellow.
The goal is to protect rest api again Cross-site request forgery attak, using double submit pattern (stateless).
Sometimes you want save your Jwt/Branca token in a http only cookie. Since it's not possible to grab it, your payload content is safe. It's particularly true for JWT who have no-encrypted payload. BUT, this protection expose your api to CSRF attack.
When a user authenticate to a site
- generate an anti-csrf
token
with pseudorandom value - generate
JWT
orBranca
and set one of payload attribute with the previouslytoken
generated - send
JWT
orBranca
to frontend in ahttp-only
,secure
cookie. - send the previously
token
generated in the response body
When an authenticated api consumer want access to your api, you need to attach the anti-csrf token
as
- eventually a cookie with unique name
- a header proprieties
- a request body parameter
For all unsafe operation [POST | PUT | PATCH | DELETE]
to you api, the middleware inspect both token
and JWT
or Branca
in http-only
cookie to check if value match
and return 401 status if not.
- dflydev-fig-cookies
- tuupola/callable-handler
- tuupola/http-factory
- rybakit/msgpack
- php-fig standards
composer require tigerwill90/xsrf-middleware
Configuration options are passed as an array. There is no mandatory parameter.
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([]));
When a request is made, the middleware inspect both token and cookie to check if value match. If cookie or token
is not found, the server will respond with 401 Unauthorized
The optional path
parameter allows you to specify which ressources of your api is protected by
the double submit pattern. It can be either a string or an array. You do not need to specify each URL.
Default parameter is /
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => "/api" /* or ["/api", "/admin"]*/
]));
In this example, everything starting with /api
will be protected.
The optional passthrough
parameter allows you to specify an exceptions to path
parameter.
It can be either a string or an array.
Default parameter is null
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"passthrough" => "/api/orders"
]));
In this example, everything starting with /api
and /admin
will be protected, except /api/orders
The optional anticsrf
parameter allow you to specify the name of your anti-csrf cookie, header or parameter.
Default parameter is xCsrf
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"anticsrf" => "xCsrf"
]));
In this example, if the cookie, header or request parameter "xCsrf" exist, the middleware will compare his value with
the specified JWT/Branca token claim
value.
According to PSR-7 JWT Authentication Middleware documentation, when the token
is decoded successfully and authentication succees, the contents of decoded token is saved as attribute
to the $request
. The optional token
parameter allows you to specify the attribute name of JWT/Branca token
that the middleware needs to find in $request
.
Default parameter is token
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"token" => "jwt"
]));
Alternatively you can pass the contents of decoded token in the optional payload
parameter.
Default value is null
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"payload" => $container["decoded"]
]));
Beauty of JWT/Branca is that you can pass extra data in the token such roles, rights, etc... Therby, we can
compare a specified claims with httponly
cookie.
[
"uid" => 1,
"iat" => "1428819941",
"exp" => "1744352741",
"aud" => "www.example.com",
"roles" => [1,0,1,1,1],
"xsrf" => "thepseudorandomvaluegeneratedforbothcookieandtoken"
]
The optional claim
parameter allows you to specify the name of the claim
that the middleware need to find in decoded JWT/Branca token.
Default value is csrf
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"claim" => "xsrf"
]));
According to this example, when a request is send to your api, you should have in the header a
httponly
cookie and an authorization
token who have both thepseudorandomvaluegeneratedforbothcookieandtoken
setted as value.
The optional logger
parameter allows you to pass a PSR-3 compatible logger to deal with debugging.
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;
$app = new Slim\App
$logger = new Logger("slim");
$formatter = new LineFormatter(
"[%datetime%] [%level_name%]: %message% %context%\n",
null,
true,
true
);
$rotating = new RotatingFileHandler(__DIR__ . "/logs/xsrf.log", 0, Logger::DEBUG);
$rotating->setFormatter($formatter);
$logger->pushHandler($rotating);
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"claim" => "xsrf",
"logger" => $logger
]));
In this example we pass an instance of Logger $logger
to the middleware.
[2017-12-06 01:14:05] [WARNING]: Payload not found in parameter
[2017-12-06 01:14:05] [DEBUG]: Token and cookie don't match, access denied !
Error is called when access is denied. It receives last error message in arguments.
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"claim" => "xsrf",
"error" => function ($response, $arguments) {
$data["message"] = $arguments["message];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data));
}
]));
The optional msgpack
parameter allows you to use the MessagePack serialization format.
Default value is false
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"payload" => $container["decoded"]
"msgpack" => true
]));
Branca/JWT Authentication Middleware need to run before Xsrf Middleware protection.
$container = $app->getContainer();
$container["XsrfProtection"] = function($c) {
function new \Tigerwill90\Middleware\XsrfProtection([
"path" => "/api",
"passthrough" => ["/api/users/signin", "/api/users/token"],
"anticsrf" => "xCsrf",
"token" => "jwt",
"claim" => "xsrf"
]);
};
$container["JwtAuthentication"] = function($c) {
return new \Slim\Middleware\JwtAuthentication([
"secure" => true,
"path" => "/api",
"passthrough" => ["/api/users/signin", "/api/users/token"],
"attribute" => "jwt",
"secret" => getenv("JWT_SECRET")
]);
};
$app->add("XsrfProtection");
$app->add("JwtAuthentication");
phpunit
The MIT License (MIT). Please see License File for more information.