This middleware implements a JSON Web Token Authentication Middleware for Slim Framework v2. For Slim 3 version see 2.x branch.
It does not implement OAuth 2.0 authorization server nor does it provide ways to generate, issue or store authentication tokens. It only parses and authenticates a token when passed via header or cookie. This is useful, for example, when you want to use JSON Web Tokens as API keys.
Install Slim 2 version using composer.
$ composer require tuupola/slim-jwt-auth:^1.0
Also add the following to the .htaccess
file. Otherwise PHP wont have access to Authorization: Bearer
header.
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Configuration options are passed as an array. The only mandatory parameter is secret
which is used for verifying then token signature. For simplicity's sake examples show secret
hardcoded in code. In real life you should use dotenv or something similar instead.
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub"
]));
An example where your secret is stored as an environment variable:
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => getenv("JWT_SECRET")
]));
When a request is made, the middleware tries to validate and decode the token. If a token is not found, the server will respond with 401 Unauthorized
. If a token exists but there is an error when validating and decoding it, the server will respond with 400 Bad Request
.
Validation errors are triggered when the token has been tampered with or the token has expired. For all possible validation errors, see JWT library source.
The optional path
parameter allows you to specify the "protected" part of your website. It can be either a string or an array.
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api", /* or ["/api", "/admin"] */
"secret" => "supersecretkeyyoushouldnotcommittogithub"
]));
The optional logger
parameter allows you to pass in a PSR-3 compatible logger to help with debugging or other application logging needs.
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api",
"logger" => $monolog,
"secret" => "supersecretkeyyoushouldnotcommittogithub"
]));
The optional rules
parameter allows you to pass in rules which define whether the request should be authenticated or not. Rule is a callable which receives the Slim app as parameter. If the callable returns boolean false
request will not be authenticated.
By default middleware configuration looks like this. All paths are authenticated with all request methods except OPTIONS
.
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"rules" => [
new \Slim\Middleware\JwtAuthentication\RequestPathRule([
"path" => "/",
"passthrough" => []
]),
new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
"passthrough" => ["OPTIONS"]
])
]
]))
RequestPathRule contains both a path
parameter and a passthrough
parameter of paths which should not be authenticated. RequestMethodRule contains passthrough
parameter of request methods which also should not be authenticated. Think of passthrough
as a whitelist.
Example use case for this is an API. Token can be retrieved via HTTP Basic Auth protected address. There also is an unprotected url for pinging. Rest of the API is protected by the JWT middleware.
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"logger" => $logger,
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"rules" => [
new \Slim\Middleware\JwtAuthentication\RequestPathRule([
"path" => "/api",
"passthrough" => ["/api/token", "/api/ping"]
]),
new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
"passthrough" => ["OPTIONS"]
])
]));
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => "/api/token",
"users" => [
"user" => "password"
]
]));
$app->post("/token", function () use ($app) {
/* Here generate and return JWT to the client. */
});
JSON Web Tokens are essentially passwords. You should treat them as such and you should always use HTTPS. If the middleware detects insecure usage over HTTP it will throw a RuntimeException
. This rule is relaxed for requests on localhost. To allow insecure usage you must enable it manually by setting secure
to false
.
$app->add(new \Slim\Middleware\JwtAuthentication([
"secure" => false,
"secret" => "supersecretkeyyoushouldnotcommittogithub"
]));
Alternatively you can list your development host to have relaxed security.
$app->add(new \Slim\Middleware\JwtAuthentication([
"secure" => true,
"relaxed" => ["localhost", "dev.example.com"],
"secret" => "supersecretkeyyoushouldnotcommittogithub"
]));
By default middleware only authenticates. This is not very interesting. Beauty of JWT is you can pass extra data in the token. This data can include for example scope which can be used for authorization.
It is up to you to implement how token data is stored or possible authorization implemented.
Let assume you have token which includes data for scope. In middleware callback you store the decoded token data to $app->jwt
and later use it for authorization.
[
"iat" => "1428819941",
"exp" => "1744352741",
"scope" => ["read", "write", "delete"]
]
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"callback" => function ($options) use ($app) {
$app->jwt = $options["decoded"];
}
]));
$app->delete("/item/:id", function () use ($app) {
if (in_array("delete", $app->jwt->scope)) {
/* Code for deleting item */
} else {
/* No scope so respond with 401 Unauthorized */
$app->response->status(401);
}
});
Tokens are like passwords and should be treated like one. Sometimes bad things happen. Maybe you accidentally leak internal token with full admin rights scope and expiration set to 10 years. Since there is no need to store tokens in database how can you revoke one? Changing the secret key would revoke all tokens in the wild.
Again it is up to you to implement how token blacklisting is done. This middleware intentionally only provides interface for blacklisting.
You can blacklist tokens by passing a callable blacklist
parameter. This callable receives the decoded token and instance of Slim application as parameters. If callable returns boolean true
the token is assumed to be blacklisted and server will response with 401 Unauthorized
.
One way of support blacklisting is to include jti
(JWT ID) claim in the token. This claim is unique identifier for the token and can be used for blacklisting.
[
"iat" => "1428819941",
"exp" => "1744352741",
"jti" => "24d4e5c5-5727-4b7f-bd1d-a8f0733f160b",
"scope" => ["read", "write", "delete"]
]
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"blacklist" => function ($options) use ($app) {
$decoded = $options["decoded"];
return "24d4e5c5-5727-4b7f-bd1d-a8f0733f160b" === $decoded["jti"];
}
]));
You can run tests either manually...
$ vendor/bin/phpunit
$ vendor/bin/phpcs --standard=PSR2 src/ -p
... or automatically on every code change.
$ npm install
$ grunt watch
Please see CONTRIBUTING for details.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.