Code Monkey home page Code Monkey logo

slim-jwt-auth's Introduction

PSR-7 and PSR-15 JWT Authentication Middleware

This middleware implements JSON Web Token Authentication. It was originally developed for Slim but can be used with any framework using PSR-7 and PSR-15 style middlewares. It has been tested with Slim Framework and Zend Expressive.

Latest Version Packagist Software License Build Status Coverage

Heads up! You are reading documentation for 3.x branch which is PHP 7.1 and up only. If you are using older version of PHP see the 2.x branch. These two branches are not backwards compatible, see UPGRADING for instructions how to upgrade.

Middleware 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.

For example implementation see Slim API Skeleton.

Install

Install latest version using composer.

$ composer require tuupola/slim-jwt-auth

If using Apache add the following to the .htaccess file. Otherwise PHP wont have access to Authorization: Bearer header.

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Usage

Configuration options are passed as an array. The only mandatory parameter is secret which is used for verifying the token signature. Note again that secret is not the token. It is the secret you use to sign the token.

For simplicity's sake examples show secret hardcoded in code. In real life you should store it somewhere else. Good option is environment variable. You can use dotenv or something similar for development. Examples assume you are using Slim Framework.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

An example where your secret is stored as an environment variable:

$app = new Slim\App;

$app->add(new Tuupola\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 or there is an error when validating and decoding it, the server will respond with 401 Unauthorized.

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.

Optional parameters

Path

The optional path parameter allows you to specify the protected part of your website. It can be either a string or an array. You do not need to specify each URL. Instead think of path setting as a folder. In the example below everything starting with /api will be authenticated. If you do not define path all routes will be protected.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "path" => "/api", /* or ["/api", "/admin"] */
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Ignore

With optional ignore parameter you can make exceptions to path parameter. In the example below everything starting with /api and /admin will be authenticated with the exception of /api/token and /admin/ping which will not be authenticated.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "path" => ["/api", "/admin"],
    "ignore" => ["/api/token", "/admin/ping"],
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Header

By default middleware tries to find the token from Authorization header. You can change header name using the header parameter.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "header" => "X-Token",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Regexp

By default the middleware assumes the value of the header is in Bearer <token> format. You can change this behaviour with regexp parameter. For example if you have custom header such as X-Token: <token> you should pass both header and regexp parameters.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "header" => "X-Token",
    "regexp" => "/(.*)/",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Cookie

If token is not found from neither environment or header, the middleware tries to find it from cookie named token. You can change cookie name using cookie parameter.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "cookie" => "nekot",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Algorithm

You can set supported algorithms via algorithm parameter. This can be either string or array of strings. Default value is ["HS256", "HS512", "HS384"]. Supported algorithms are HS256, HS384, HS512 and RS256. Note that enabling both HS256 and RS256 is a security risk.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "algorithm" => ["HS256", "HS384"]
]));

Attribute

When the token is decoded successfully and authentication succeeds the contents of the decoded token is saved as token attribute to the $request object. You can change this with. attribute parameter. Set to null or false to disable this behavour

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "attribute" => "jwt",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

/* ... */

$decoded = $request->getAttribute("jwt");

Logger

The optional logger parameter allows you to pass in a PSR-3 compatible logger to help with debugging or other application logging needs.

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

$app = new Slim\App;

$logger = new Logger("slim");
$rotating = new RotatingFileHandler(__DIR__ . "/logs/slim.log", 0, Logger::DEBUG);
$logger->pushHandler($rotating);

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "path" => "/api",
    "logger" => $logger,
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Before

Before function is called only when authentication succeeds but before the next incoming middleware is called. You can use this to alter the request before passing it to the next incoming middleware in the stack. If it returns anything else than Psr\Http\Message\ServerRequestInterface the return value will be ignored.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "before" => function ($request, $arguments) {
        return $request->withAttribute("test", "test");
    }
]));

After

After function is called only when authentication succeeds and after the incoming middleware stack has been called. You can use this to alter the response before passing it next outgoing middleware in the stack. If it returns anything else than Psr\Http\Message\ResponseInterface the return value will be ignored.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "after" => function ($response, $arguments) {
        return $response->withHeader("X-Brawndo", "plants crave");
    }
]));

Note that both the after and before callback functions receive the raw token string as well as the decoded claims through the $arguments argument.

Error

Error is called when authentication fails. It receives last error message in arguments. You can use this for example to return JSON formatted error responses.

$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "error" => function ($response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];

        $response->getBody()->write(
            json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
        );

        return $response->withHeader("Content-Type", "application/json")
    }
]));

Rules

The optional rules parameter allows you to pass in rules which define whether the request should be authenticated or not. A rule is a callable which receives the request as parameter. If any of the rules returns boolean false the 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\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "rules" => [
        new Tuupola\Middleware\JwtAuthentication\RequestPathRule([
            "path" => "/",
            "ignore" => []
        ]),
        new Tuupola\Middleware\JwtAuthentication\RequestMethodRule([
            "ignore" => ["OPTIONS"]
        ])
    ]
]));

RequestPathRule contains both a path parameter and a ignore parameter. Latter contains paths which should not be authenticated. RequestMethodRule contains ignore parameter of request methods which also should not be authenticated. Think of ignore as a whitelist.

99% of the cases you do not need to use the rules parameter. It is only provided for special cases when defaults do not suffice.

Security

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. By default this rule is relaxed for requests to server running on localhost. To allow insecure usage you must enable it manually by setting secure to false.

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secure" => false,
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Alternatively you could list multiple development servers to have relaxed security. With below settings both localhost and dev.example.com allow incoming unencrypted requests.

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secure" => true,
    "relaxed" => ["localhost", "dev.example.com"],
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

Authorization

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. By default middleware saves the contents of the token to token attribute of the request.

[
    "iat" => "1428819941",
    "exp" => "1744352741",
    "scope" => ["read", "write", "delete"]
]
$app = new Slim\App;

$app->add(new Tuupola\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

$app->delete("/item/{id}", function ($request, $response, $arguments) {
    $token = $request->getAttribute("token");
    if (in_array("delete", $token["scope"])) {
        /* Code for deleting item */
    } else {
        /* No scope so respond with 401 Unauthorized */
        return $response->withStatus(401);
    }
});

Testing

You can run tests either manually or automatically on every code change. Automatic tests require entr to work.

$ make test
$ brew install entr
$ make watch

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.

slim-jwt-auth's People

Contributors

bafs avatar bezumkin avatar byan avatar dakujem avatar jhmoon2000 avatar jimtools avatar jv-k avatar klarsson avatar orx0r avatar timesplinter avatar tuefekci avatar tuupola avatar xu42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slim-jwt-auth's Issues

More Flexible RequestPathRule

Right now I can authenticate for a top-level route and passthrough sub-routes. From the documentation:

new RequestPathRule([
    "path" => "/api",
    "passthrough" => ["/api/token", "/api/ping"]
]),

But what I want is the reverse, to authenticate sub-routes, and passthrough a top-level route. I realize this is an unrealistic example, but just for comparison's sake:

new RequestPathRule([
    "path" => ["/api/token", "/api/ping"]
    "passthrough" => ["/api"]
]),

This would allow me to do things like POST /users/ unauthenticated, for sign-ups, but then require requests to GET /users/me to be authenticated.

Unknown JSON error, but only on localhost

Thanks for the amazing plugin. I've managed to get it up and working with Slim v3 without any issue. The original implementation worked flawlessly, where I verify and decode a token in the Authorization header in the format of Bearer: <token>.

I am in the midst of adding another layer of Middleware, which will verify and decode an API access JSON web token. I have validated this JWT on jwt.io and it is properly formatted and can be successfully verified with a secret that I use. However, as the Authorization header is already used for a user token, the API JWT is provided via the X-API-KEY header, in the format of: X-API-KEY: <token>, sans the Bearer prefix.

For some reason, this JWT can be decoded successfully on my production server, but never on a development server hosted locally on localhost. I keep getting a unknown JSON error: 5 in the $arguments['message'] key. My implementation is as follow:

// Middleware for user token authentication
// This layer works all the time :) 
$api->add(new \Slim\Middleware\JwtAuthentication([
    'secret' => JWT_SECRET,
    'path' => ['/admin', '/users'],
    'secure' => true,
    'relaxed' => ['localhost'],
    'attribute' => 'user_auth_token',
    'error' => function($request, $response, $arguments) use ($c) {
        return $c['response']
            ->withStatus(401)
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode(array(
                'status' => 401,
                'message' => 'User authentication failed. '.$arguments['message']
                )));
    }
    ]));

// Middleware for API access token authentication
// This layers derps only when both Authorization and X-API-KEY headers are defined, and only on localhost
$api->add(new \Slim\Middleware\JwtAuthentication([
    'secret' => JWT_SECRET,
    'header' => 'X-API-KEY',
    'regexp' => '/(.*)/',
    'secure' => true,
    'relaxed' => ['localhost'],
    'attribute' => 'access_token',
    'error' => function($request, $response, $arguments) use ($c) {
        return $c['response']
            ->withStatus(401)
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode(array(
                'status' => 401,
                'message' => 'Access token verification failed. '.$arguments['message'],
                'token' => $request->getHeaders()['HTTP_X_API_KEY']
                )));
    }
    ]));

Interestingly, the token returned in the error callback is valid. So the X-API-KEY header is being correctly parsed and understood. Perhaps you might have an idea what is going wrong internally? I have tried installing Monolog, but the log file isn't showing up anywhere.

Some other tests:

  1. Swapping the order of the middleware layer (whether user token or access token are verified in either order) does not make the error go away.
  2. Removing the Authorization header (and the associated middleware layer) fixes the problem (i.e. having the X-API-KEY issued only, will cause the JSON error to go away). However, the error is localized to the middleware layer that verifies the X-API-KEY header, not the Authorization header.
  3. Removing the X-API-KEY header (but not the associated middleware layer) causes requests to passthrough. This should not happen.
  4. Manually verifying the X-API-KEY header value in a custom middleware layer and using other libraries fixed the issue.

My hypothesis is that when both headers are used in conjuction, the JwtAuthentication middleware derps. However, I am unsure why this happens, nor can I explain it programatically.

Set passthrough for each route

I am using slim 2, and I want allow the route straight from a middleware, something like this...

//public access
$app->get('/user/:id/public', new SomeMiddlewareToAuthorizeDirectAccess(), function ($id) use () {
    return ...
});

//private/auth access
$app->get('/user/:id', function ($id) use () {
    return ...
});

Or even a way to set path on passthrough with keys

$app->add(new \Slim\Middleware\JwtAuthentication([
    "logger" => $logger,
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "rules" => [
        new RequestPathRule([
            "path" => "/api",
            "passthrough" => ["/api/user/*/public"]
        ])
    ]
]));

It could be clear?
Thanks guys

Callback function should be always triggered, even on passthrough routes

I think it would be very useful if callback function is always triggered, being a protected or passthrough route, having or not having a JWT to decode, so we can handle the situation according to the parameters we receive in this callback.

If there is a successful decoded token, it will come in the callback params, and if there is no token it will just not come. Then, we can make decision accordingly.

The problem with not triggering the callback function when on a passthrough route is that I lose the chance to set some useful variables and properties that would be important when a route is passed through.

Google jwt authentication

Did anyone extended the JwtAuthentication class to work as a GoogleJwtAuthentication ?
I mean, i have an android app that gets Google token, then i want to send it to my API (slim) server to get my own jwt token from api.

Syntax Error and 'use' missing

Hi, in the part of rules is missing one "]" for close $app->add(new \Slim\Middleware\JwtAuthentication([ and also is missing "use \Slim\Middleware\JwtAuthentication\RequestMethodRule; use \Slim\Middleware\JwtAuthentication\RequestPathRule;" if not show an error of class

Greets

$this->decodeToken failed should return 401 unauthorised

Any exception that encountered in decoding token should be regarded as unauthorised, in order to cater for exceptions that is returned from JWT: ExpiredException, BeforeValidException and SignatureInvalidException. Generally malformed token should be regarded as unauthorised as well.

404 error on request

Hi,
i get a 404 error when i make a get request to the api when using with angular 2, at first i felt it was problem with angular 2 service. But when i tested with other api's it works, but gives 404 error with a response saying
{message: "Not found"}
Its an issue on the api side what am i doing wrong, is it the cors issue, Please help

Slim 3 - Fatal error: Cannot use object of type Slim\Http\Request as array

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "callback" => function ($options) use ($app) {
        $app->jwt = $options["decoded"];
    }
]));

The example above doesn't work anymore. Maybe Slim 3 is overriding properties or don't allow to set the property. Just to remeber, Slim 3 changed some concepts, since DI container appears, is not possible get app instance like this: $app = \Slim\Slim::getInstance();

error $callable JWT

hi tuupola.
i use jwt auth form your repo but i have error code in here

public function shouldAuthenticate()
    {
        /* If any of the rules in stack return false will not authenticate */
        foreach ($this->rules as $callable) {
            if (false === $callable($this->app)) {
                return false;
            }
        }
        return true;
    }

in above error function name must be callable - a string
because class $callable not implement constructor or __invoke . any have fix for that?
thanks

fetchToken() is not compatible with Slim's cookie encryption

For example, the return value for fetchToken() is return $_COOKIE[$this->options["cookie"]];, but this direct access of $_COOKIE invalidates Slim's cookie helper, including cookie encryption. This makes it impossible to use encrypted cookies for storing tokens. Shouldn't the return statement be: return $this->app->getCookie($this->options["cookie"]) ?

Slim 3 passthrough all routes when installed in subfolder

When no public routes set, middleware runs ok, but when add the following:

    "rules" => [
        new \Slim\Middleware\JwtAuthentication\RequestPathRule([
            "path" => "/",
            "passthrough" => ["/api/ping"]
        ]),
        new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
            "passthrough" => ["OPTIONS"]
        ])
    ]

Middleware just don't check JWT and passthrough all routes. The same code works fine on Slim 2, but Slim 3 don't.

Ps: Works fine on php built-in server, (without any dot (.) char in uri params), Apache doesn't work, passthrough all routes. Current .htaccess:

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [QSA,L]
    RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>

HttpBasicAuthentication class doesn't exist

In your example, you have this snippet:

$app->add(new \Slim\Middleware\HttpBasicAuthentication([
    "path" => "/api/token",
    "users" => [
        "user" => "password"
    ]
]));

But the HttpBasicAuthentication class is nowhere to be found

Slim Error on redirecting from error callback

I have a strange issue. Thats the error callback of the JWT JwtAuthentication (1.x).

"error" => function ($options) use ($app) {
        return $app->redirect( $app->urlFor('index') );
    }

On my development system (mamp php 5.6) the rediect from the "error" callback works fine.
On my productions erver (1&1 php 5.6) I get the following error:

Type: Slim\Exception\Stop
File: /SERVERPATH/htdocs/app/vendor_modules/slim/slim/Slim/Slim.php
Line: 1022
Trace

#0 /SERVERPATH/htdocs/app/vendor_modules/slim/slim/Slim/Slim.php(1042): Slim\Slim->stop()
#1 /SERVERPATH/htdocs/app/vendor_modules/slim/slim/Slim/Slim.php(1105): Slim\Slim->halt(302)
#2 /SERVERPATH/htdocs/app/php/start.php(151): Slim\Slim->redirect('/app/public/')
#3 /SERVERPATH/htdocs/app/vendor_modules/tuupola/slim-jwt-auth/src/JwtAuthentication.php(150): {closure}(Array)
#4 /SERVERPATH/htdocs/app/vendor_modules/tuupola/slim-jwt-auth/src/JwtAuthentication.php(97): Slim\Middleware\JwtAuthentication->error(Array)
#5 /SERVERPATH/htdocs/app/vendor_modules/slim/slim/Slim/Middleware/PrettyExceptions.php(67): Slim\Middleware\JwtAuthentication->call()
#6 /SERVERPATH/htdocs/app/vendor_modules/slim/slim/Slim/Slim.php(1302): Slim\Middleware\PrettyExceptions->call()
#7 /SERVERPATH/htdocs/app/php/start.php(176): Slim\Slim->run()
#8 /SERVERPATH/htdocs/app/public/index.php(2): require('/SERVERPATH/d...')
#9 {main}

What could be wrong?!

Empty Response

When executing a request without a token, the middleware returns an empty response object.

The error method checks if there is any function entered within "error" options, if it has not informed an empty response object is returned.

Decode JWT Token (if defined) even if the route is defined as passthrough

I've got this problem while developing my application...

I have a public route, defined as passthrough route in my JWT configuration, but, even that these route is a public/passthrough route, I want to register some logs and other stuff related to the user's actions in that page, if the user is already logged and passes a valid token to this opened route.

The problem is that, when routes are defined as passthrough, the defined callback function is just ignored and never triggers. So, my token is never decoded, even if I pass it by header or environment variable.

So, I think it should be good to have tokens decoded properly on passthrough routes, in such a way that it is not mandatory to have them, but, if they are there, i can have a way to handle them and, maybe, give some additional stuff to that logged user.

For now, I solved my problem making a middleware that comes just after JwtAuthentication middleware, and that tries to find the $app->jwt defined in the previous callback. If it just not find it, it means that the route is passthrough and that the callback function was not triggered... then I decode the JWT manually getting the header bearer token and using Firebase\JWT\JWT::decode() function to decode it properly.

It would be nice to have this issue implemented.

Anyway, thank you very much for this very useful lib.

How to add it without composer?

I have a working Slim app (added without composer), and now I want to implement slim-jwt-auth but i can't figure it out how to do it (autoloading, i think)

Is there any option to do this?
If so, what are the steps that I need to follow in order to achieve the integration of Slim and Slim-jwt-auth.

Any help would be very appreciated ๐Ÿ‘

Feature request: validate token on passthrough routes

I recently ran into a scenario where it would be handy to have decode a JWT token for anonymous routes, if it's available. In the API I'm constructing, I'm using strict rate limiting for some anynomous calls, and rate limits are raised when a user's logged in.

However, the middleware doesn't allow this at the moment, since it's an all or nothing proposition: either the route has to be authenticated, and an 401 is returned when no token is present, or a route is passthrough, and no token decoding is done at all.

I solved the problem for the moment by decoding the token in middleware that follows, but in my opinion, it would be more elegant if this could be done in this middleware.

Would you consider adding either a parameter specifying that passthrough routes get to be decoded too - without returning a 401, or alternatively to specify a third option apart from "path" and "passthrough" which allows routes to be used either anonymously or as an authenticated user?

Another option, I presume, would be to define all routes as authenticated, and allow those routes that may be anonymous to proceed without a 401 status, using the error callback, but that seems a bit kludgy to me. It also would involve rewriting the error logic, since the error handler isn't able to override the 401 status at the moment.

Passthrough with wildcards/regex

It would be nice to be able to specify wildcards in the passthrough paths (and maybe other paths too). For example, /api/some_resource/*/object would match paths such as /api/some_resource/1/object, /api/some_resource/2/object, etc.

Rejection reasons

Tried to collect possible rejection reasons to react on malformed, expired or missing tokens to use in client for proper error handling.

Got these from exceptions declared in the lib itself, are there more?

  1. Token not found
  2. Expired token
  3. Signature verification failed

No secure cookie usage?!

Just curious, why do you use
$_COOKIE[$this->options["cookie"]]
instead of
$this->app->GetCookie($this->options["cookie"])
this way it's possible to use encrypted cookies

Token not found / apache / php

On my Slim setup (v3) with php 5.6.14, token can't be found, i need to get it via apache_request_headers() and set $_SERVER['HTTP_AUTHORIZATION'] in index.php. I've tried different .htaccess directives like the one provided here without success.

Would it be possible to check if token can be found in 'apache_request_headers' ?

/**
 * Fetch the access token
 *
 * @return string|null Base64 encoded JSON Web Token or null if not found.
 */
public function fetchToken(RequestInterface $request) 
{

   /* If using PHP in CGI mode and non standard environment */
    $server_params = $request->getServerParams();
    if (isset($server_params[ $this->options["environment"]])) 
    {
        $message = "Using token from environent";
        $header = $server_params[ $this->options["environment"]];
    }

    $header = $request->getHeader("Authorization");
    if (isset($header[0])) 
    {
        $message = "Using token from request header";
        $header = isset($header[0]) ? $header[0] : "";
    }

    /* FIX for apache */
    if (function_exists('apache_request_headers')) 
    {
        $headers = apache_request_headers();
        $header = isset($headers['Authorization']) ? $headers['Authorization'] : "";
    }

    if (preg_match("/Bearer\s+(.*)$/i", $header, $matches)) 
    {
        $this->log(LogLevel::DEBUG, $message);
        return $matches[1];
    }

    /* Bearer not found, try a cookie. */
    $cookie_params = $request->getCookieParams();

    if (isset($cookie_params[ $this->options["cookie"]])) 
    {
        $this->log(LogLevel::DEBUG, "Using token from cookie");
        $this->log(LogLevel::DEBUG, $cookie_params[ $this->options["cookie"]]);
        return $cookie_params[ $this->options["cookie"]];
    };

    /* If everything fails log and return false. */
    $this->message = "Token not found";
    $this->log(LogLevel::WARNING, $this->message);
    return false;
}

RequestPathRule passthrough?

Great work on this middleware Mike!

In reviewing it for use in one of my projects, I was curious what the best way to exclude a URI would be (i.e. /user/login). I see there is a "path" option for the RequestPathRule class that is passed in here to specify path groups, but I don't see where you are passing through the "passthrough" option here. (You are sending "passthrough" for RequestMethodRule just above this)

Am I missing something or am I attempting to solve the problem of a "login url" in a different way than you had intended.

passthrough is not respected

Tahts my code (1.x branch)

$app->add(new JwtAuthentication([
    "secret" => $app->config->get('jwt.secret'),
    "secure" => true,
    "relaxed" => ["localhost"],
    "path" => ["/edition","/photos","/files","/issues","/redaktion"],
    "passthrough" => ["/redaktion/login"],
    "callback" => function ($options) use ($app) {
        $app->jwt = $options["decoded"];
    },
    "error" => function ($options) use ($app) {
        return $app->redirect( $app->urlFor('index') );
    }
]));

But when I try to access redaktion/login I always land in the "error" callback.
Did I missed something?

Ability to set root path as passthrough

Hey,

maybe i am missing something, but it seems not to be possible to set the path to "/" and at the same time enable passthrough for "/", so that the root of the Url doen't have to be authenticated.

Am i right, or am i missing something?

Edit:
Forgot to mention WHY this isn't working. When both set to "/" all other routes are unauthenticated too. Although they are not set to passthrough.

Why 400 "Bad Request" if token exists but not decodable?

Hi,
thanks for the lib. I wounder if there is a specific reason behind returning HTTP-Status 400 when the token can not be decoded?

Background: If i request a resource with a expired token i currently get a 400 (instead of 401). But 400 has, at least for me, the meaning: the request has a bad syntax or can not be processed. I would assume a 401: something wrong with the authentification.

https://github.com/tuupola/slim-jwt-auth/blob/master/src/JwtAuthentication.php#L99
In the code you already checked if a token exists (and throw a 401 if not). After that you throw a 400, if it is noch decodable. Is this common practice?

Insecure decode of JWT

In JWTAuthentication.php, Firebase\JWT is called in the following manner:

        return JWT::decode(
            $token,
            $this->options["secret"],
            ["HS256", "HS512", "HS384", "RS256"]
        );

Let's assume that a token has been signed with an RSA-256 private key, and that the corresponding public key is passed as a secret to the JWTAuthentication class. If an attacker were to craft a new JWT token containing { "alg": "HS256" } in the header and sign it with HMAC using the public key as a secret, this would result in the token being erroneously validated. Since public keys are, by their nature, meant to be transmitted, it isn't inconceivable that an attacker would be able to get hold of a public key.

Possible workarounds would be to add either a mandatory "algorithm" key to the options array, or even better to make it mandatory to provide a "kid" (key ID) in the header, since this allows the decoder to map a key ID to the corresponding algorithm.

Retry on JWT decoding failure.

Firstly, thanks very much for this great project. It works very well!

I have an edge case where I am using openid connect to get the public key for the signature in jwks format (from from jwks_uri in /.well-known/openid-configuration) and I convert this key to PEM format so slim-jwt-auth can decode.

The problem I have is, if the keys are rotated on the provider, and I cache the converted PEM key to decode the token, I'd like to have slim-jwt-auth call some kind of retry callback on decode failure, so I can change the configuration to fetch the jwk again and convert it to a PEM and set it as the secret, then try to decode again. It currently sends a 401 not authorized on failure.

My current workaround is just to decode the token, and if that is okay, slim-jwt-auth will decode it again (adds overhead).

Is this a practical solution?

Path option for case insensitive

Slim has an option to make routes case sensitive or not. I turned this feature on and it took me awhile to figure out why the JWT token wasn't in $app.

$app->config([ 'routes.case_sensitive' => false ]);

I suggest adding a 'path_case_sensitive' option to the options array.

If you'd like a PR I can send it your way.

More complex path and passthrough

I really like this middleware!

One feature I'm missing is to define more complex PathRules for an REST API.

It would be nice if one can tell the passthrough on which HTTP method it should be possible.

Example:

  • I want to be able to passthrough all GET requests except one/two/three.
  • I want to passthrough only POST on route "/login" not all

Is there any way to accomplish this currently?

Callback should act like a Slim middleware.

I'm using this middleware to verify a token.

In addition to that, I would like to resolve the user account the token has been issued to and attach this to the request, so that the controller function that is called for the given route has access to the user account.

Using this middleware and the callback method, I have no way of manipulating the $request like middleware does.

I also had a quick chat with Akrabat on the Slim IRC:

npx:     hey qq, what would be the preferred way to have a middleware add an argument to the $args (third parameter of a controller method call) [...]
Akrabat: $request = $request->withAttribute('token', $token); 
         It should turn up in $args, but will certainly be in $request->getAttribute('token'); 
npx:     just noticed that tuupola's middleware's callback does not give me the option to manipulate the request haha 
[...]
Akrabat: yeah - tuupola should update that really 
         ideally, the callback would act as middleware 
         or put decoded into the request's attributes anyway 

Suggestions:

  1. if the decoded token is added to the Request's attributes, a Middleware following this one can access the decoded token and do as it pleases. However, that makes the second middleware depended on this one.

  2. change callback or add another callback option that makes the provided function act like a middleware and return the call to $next($request, $response). In this case the function has to handle the error on its own, while it of course would be nice to keep the error handling in this middleware to keep it consistent. Alternatively allow throwing an Exception that will be caught.

  3. have callback return false or an array($request, $response).
    On false return the error response like before.
    On null or true call $next($request, $response) like nothing happened
    and if the array is returned call $next($request, $response) with the values of the array.

Please discuss if you can see other solutions, this was just a quick draft of the top of my head as I have to rush now.

I can play with this more extensively tomorrow and provide a pull request if requested.

Implementation for mobile?

Hey there guys,
Am wondering if a JWT can be implemented for REST calls from a mobile device.

As of now, i can generate JWT tokens for browser based applications and it works pretty well. But I can't figure out how to do this for a mobile based application; any suggestions?

Thank you.

RequestPathRule doesn't work with conditionial parameters

RequestPathRule allows only pretty simple URLs, so I solved it by adding:

    $uri     = $app->request->getResourceUri();
    $match   = reset($app->router->getMatchedRoutes($app->request->getMethod(), $uri));
    $pattern = is_object($match) ? $match->getPattern() : "";

and for the checks:
if (!!preg_match("@^{$passthrough}(/.*)?$@", $uri) || $passthrough == $pattern) {
and
if (!!preg_match("@^{$path}(/.*)?$@", $uri) || $path == $pattern) {

It will now correctly detect URL patterns such as '/cart(/:cartId)/add/product/:prodId(/:sizeId)

Edit: I forgot to mention that I'm using the 1.x branch with SLIM 2 - but doesn't look like it differs much from the 2.x branch and some similar solution should be possible.

Token passing in URL

I would like to create URL which contains token.
So I would like to know if there is a way to do that by passing token as query parameters for example ?

Refresh token

Hey guys,

There any way to use refresh token with this lib?

If doesn't, how the best practice to implement that?

Sorry for my noob

Issue with matched token in Firebase JWT

Any ideas why my token auth is giving me this?

<br />
<b>Fatal error</b>: Cannot access property started with '\0' in
<b>[..]/vendor/firebase/php-jwt/Authentication/JWT.php</b>on
line
<b>237</b>
<br />

Using it with other psr7 stack then slim

Hello,

I give a look to the Readme and it seems like we could use this library with other psr7 stack builder that match the same middleware signature then Slim.
Am I wrong ?

Add error callback for 1.x Branch

I tryed to add an error callback to the 1.x branch and it worked on my local server but when I uploaded it on an another one i got an slim error.
PHP Version is the same (5.6).

This code I added in jwtAuthetification.php @ line 99

if (false === $decoded = $this->decodeToken($token)) {
    if (is_callable($this->options["error"])) {
        $params = array("decoded" => $decoded, "app" => $this->app);
        if (false === $this->options["error"]($params)) {
            $this->app->response->status(401);
            $this->error(array(
                "message" => "Callback returned false"
            ));
            return;
        }
    }

    $this->app->response->status(400);
    $this->error(array(
        "message" => $this->message
    ));
    return;
}

then my JwtAuthetification setup looks like this:

$app->add(new JwtAuthentication([
    "secret" => $app->config->get('jwt.secret'),
    "secure" => false,
    "relaxed" => ["localhost","www"],
    "path" => ["/edition","/photos","/files","/issues"],
    "callback" => function ($options) use ($app) {
        $app->jwt = $options["decoded"];
    },
    "error" => function ($options) use ($app) {
        $app->setCookie('deeplink', $_SERVER['REQUEST_URI'] );
        return $app->redirect( $app->urlFor('index') );
    }
]));

and thats the error I get on the other server

Details

Type: Slim\Exception\Stop
File: /homepages/htdocs/app/vendor/slim/slim/Slim/Slim.php
Line: 1022
Trace

#0 /homepages/htdocs/cypos/vendor/slim/slim/Slim/Slim.php(1042): Slim\Slim->stop()
#1 /homepages/htdocs/cypos/vendor/slim/slim/Slim/Slim.php(1105): Slim\Slim->halt(302)
#2 /homepages/htdocs/cypos/php/start.php(137): Slim\Slim->redirect('/')
#3 /homepages/htdocs/cypos/vendor/tuupola/slim-jwt-auth/src/JwtAuthentication.php(104): {closure}(Array)
#4 /homepages/htdocs/cypos/vendor/slim/slim/Slim/Middleware/PrettyExceptions.php(67): Slim\Middleware\JwtAuthentication->call()
#5 /homepages/htdocs/cypos/vendor/slim/slim/Slim/Slim.php(1302): Slim\Middleware\PrettyExceptions->call()
#6 /homepages/htdocs/cypos/php/start.php(163): Slim\Slim->run()
#7 /homepages/htdocs/cypos/public/index.php(2): require('/homepages/33/d...')
#8 {main}

I currently cant migrate to slim3 to upgrade this middleware to 2.x.

Allow Multiple Secrets

I can't find this in the documentation, so I was wondering how one would go about accepting multiple JWT Secrets. For example, I would like to generate a new secret for every client who I give access.

Client1 => 'client1secret'

Client2 => 'client2secret'

Is this possible?

Adding extra slashes after domain name in url allows you to bypass JWT Authentication

The following array was passed to JwtAuthentication class constructor:

$options = [
    "secret" => $_ENV["JWT_SECRET"],
    "path" => ["/api/v1"],
    "passthrough" => ["/api/v1/login"]
]

Sending an HTTP request to "http://localhost/api/v1/restricted" returns HTTP 401 Unauthorized but for some reason I am able to bypass JWT authentication by adding one or more extra slashes after the domain name. e.g. "http://localhost//api/v1/restricted"

Not working with Slim 3

- tuupola/slim-jwt-auth 0.3.0 requires slim/slim ~2.3 -> satisfiable by slim/slim[2.x-dev].
- tuupola/slim-jwt-auth 0.3.0 requires slim/slim ~2.3 -> satisfiable by slim/slim[2.x-dev].
- tuupola/slim-jwt-auth 0.3.0 requires slim/slim ~2.3 -> satisfiable by slim/slim[2.x-dev].
- Can only install one of: slim/slim[3.x-dev, 2.x-dev].
- Installation request for slim/slim 3.x-dev -> satisfiable by slim/slim[3.x-dev].
- Installation request for tuupola/slim-jwt-auth ^0.3.0 -> satisfiable by tuupola/slim-jwt-auth[0.3.0].

Installation failed, reverting ./composer.json to its original content.

Return of Data

Hello,

Im working with a restful api where i plan to use this lib in. But i have some challanges whiout hevey changes of your code. When you do your checks for the tokens and they are not valid, you just return the response code and no more data.

Is ther a way you cold add in support for return of data in the checks? Like a array input in the code with default data you want, then you can ether set a standar output message or allow it to be set when you open the Middleware?

I need some help

Heu Tuupola,

i want to implemente your library in my Slim Web API but i don't understand, how to return the token when i try to send a request for login (If the access are good ! )?

Thanks :) ! (Sorry my english is not very good aha )

Can't get the Authorization to work.

I love the idea behind slim-jwt-auth and i'm trying to use it for my API, but i'm completely stuck trying to get my supersecret key to even validate.

Somehow it is not clear to me, how to properly pass the secret key to the Middleware for it to properly authenticate it, and respond.

I already added the configuration needed to the .htaccess.

Maybe i'm doing something wrong ? Do i need to encode the secret pass first at http://jwt.io/ ??

I have spent hours trying to understand how to properly validate this and nothing, if you can kindly point me in the right direction, i will most likely appreciate it, as it is not clear in the docs.

Thank you so much !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.