, (*1)
PSR-7 & PSR-15 : CSRF Protection alternative for JWT/Branca Authentication token
Csrf protection based on double submit pattern, cookie - JWT/Branca alternative., (*2)
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., (*3)
This middleware does not provide ways to generate Branca/JWT token. However you can find all you
needs for generate token with links bellow., (*4)
The goal is to protect rest api again Cross-site request forgery
attak, using double submit pattern (stateless)., (*5)
How it's work ?
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., (*6)
When a user authenticate to a site, (*7)
- generate an anti-csrf
token
with pseudorandom value
- generate
JWT
or Branca
and set one of payload attribute with the previously token
generated
- send
JWT
or Branca
to frontend in a http-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, (*8)
- 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., (*9)
Dependencies
Install
composer require tigerwill90/xsrf-middleware
Usage
Configuration options are passed as an array. There is no mandatory parameter., (*10)
$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
, (*11)
Optional parameters
Path
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., (*12)
Default parameter is /
, (*13)
$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., (*14)
Passthrough
The optional passthrough
parameter allows you to specify an exceptions to path
parameter.
It can be either a string or an array., (*15)
Default parameter is null
, (*16)
$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
, (*17)
AntiCsrf
The optional anticsrf
parameter allow you to specify the name of your anti-csrf cookie, header or parameter., (*18)
Default parameter is xCsrf
```php
$app = new Slim\App, (*19)
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"anticsrf" => "xCsrf"
]));
```, (*20)
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., (*21)
#### Token, (*22)
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
., (*23)
Default parameter is token
```php
$app = new Slim\App, (*24)
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"token" => "jwt"
]));
```, (*25)
#### Payload, (*26)
Alternatively you can pass the contents of decoded token in the optional payload
parameter., (*27)
Default value is null
`````php
$app = new Slim\App, (*28)
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"payload" => $container["decoded"]
]));
`````, (*29)
#### Claim
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., (*30)
```php
[
"uid" => 1,
"iat" => "1428819941",
"exp" => "1744352741",
"aud" => "www.example.com",
"roles" => [1,0,1,1,1],
"xsrf" => "thepseudorandomvaluegeneratedforbothcookieandtoken"
], (*31)
```, (*32)
The optional claim
parameter allows you to specify the name of the claim
that the middleware need to find in decoded JWT/Branca token., (*33)
Default value is csrf
, (*34)
$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., (*35)
Logger
The optional logger
parameter allows you to pass a PSR-3 compatible logger to deal with debugging., (*36)
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., (*37)
[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
Error is called when access is denied. It receives last error message in arguments., (*38)
$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));
}
]));
MessagePack
The optional msgpack
parameter allows you to use the MessagePack serialization format., (*39)
Default value is false
, (*40)
$app = new Slim\App
$app->add(new Tigerwill90\Middleware\XsrfProtection([
"path" => ["/api", "/admin"],
"payload" => $container["decoded"]
"msgpack" => true
]));
Implementation with JWT/Branca Authentication Middleware
Branca/JWT Authentication Middleware need to run before Xsrf Middleware protection., (*41)
$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");
Testing
phpunit
License
The MIT License (MIT). Please see License File for more information., (*42)