tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.28k stars 1.55k forks source link

How do you refresh Token ? #186

Closed mrgodhani closed 6 years ago

mrgodhani commented 9 years ago

Hi

Is there any documentation on how to refresh token after it gets expired ?

Also there is conflict if you use both refresh and blacklist for all protected routes.

mrgodhani commented 9 years ago

When using jwt.refresh token and Blacklist enabled both together. It just blacklists token after one request ? So what's solution for that.

tymondesigns commented 9 years ago

what version of the package/laravel are you using?

biblicalph commented 9 years ago

Using 0.5.4 with laravel 5.1. As indicated by @mrgodhani token is blacklisted after one request when using jwt.refresh token and blacklist together. Can you advise on that? Thanks

tymondesigns commented 9 years ago

@biblicalph Hmm.. sounds like a bug.. One that keeps rearing it's head incidentally. I will check it out when I'm back at my machine

mrgodhani commented 9 years ago

Any update on this ? It's really annoying. I had to separate out my all get requests to another route group without jwt.refresh.

mrgodhani commented 9 years ago

Also one more note I am not really able to refresh expired token using middleware. If it's expired it won't refresh but would throw expired error.

devjoca commented 9 years ago

Hi @mrgodhani, this code works for me:

$token = JWTAuth::getToken();
$newToken = JWTAuth::refresh($token);
mrgodhani commented 9 years ago

@jopereyral I was referring to middleware. Using middleware (jwt.refresh) it won't refresh expiredtoken.

kylestev commented 9 years ago

@mrgodhani The middleware is working as intended:

it just blacklists token after one request

From the docs:

RefreshToken This middleware will again try to parse the token from the request, and in turn will refresh the token (thus invalidating the old one) and return it as part of the next response. This essentially yields a single use token flow, which reduces the window of attack if a token is compromised, since it is only valid for the single request.


Also one more note I am not really able to refresh expired token using middleware. If it's expired it won't refresh but would throw expired error.

If you have the jwt.auth middleware applied to a route before you apply the jwt.refresh and your token is expired, the jwt.auth middleware is called before the jwt.refresh middleware and will not append the Authorization header with the new token -- instead the request aborts during the jwt.auth middleware because your token has expired. This happened to me when I had a route group for my API with the jwt.auth middleware applying to all API routes, including the endpoint I used to refresh my token when it expired.

One possible workflow is:

  1. Have a route for refreshing your tokens such as: /api/auth/refresh_token which has the jwt.refresh middleware applied but not the jwt.auth middleware.
  2. Have the clients who consume your API make a request to the refresh_token endpoint if they have a request that fails because a token has expired.
shivekkhurana commented 9 years ago

@kylestev : I tried the suggested workflow but it gives a 400 error. (I generated a new token and tried to refresh it)

Here's the route :

Route::post('auth/refresh-token', ['middleware' => 'jwt.refresh', function() {}]);

and response :

{
    "error": "token_invalid"
}
tymondesigns commented 9 years ago

@shivekkhurana are you using the latest version of this package?

felipedeaguiar commented 9 years ago

Hi, I am having the same problem , I'm using laravel 4 and have a method that updates the token but is returned token_expired , someone has been there ?

shivekkhurana commented 9 years ago

@tymondesigns : My composer.json shows "tymon/jwt-auth": "0.5.*" and I see the packagist version is 0.5.4. I set my project up just two weeks ago and 0.5.4 was released on 2015-06-23. So I guess I have the latest stable version.

PS: I created a route and a controller method, following @jopereyral 's advice. It worked fine.

TheGhoul21 commented 8 years ago

using PHPUnit the Request of JWTAuth goes missing. I needed to make a manual call to

JWTAuth::setRequest($request);

in order to have the refresh flow work.

youyi1314 commented 8 years ago

Hi @tymondesigns Laravel version is 5.1 JWT-auth version is 0.5.5 I facing same problem with : middleware'=> ['before'=>'jwt.auth','after'=>'jwt.refresh'], it work refresh for 1 or 2 time, than it refresh an invalid_token. Thanks

brazorf commented 8 years ago

There's definitely something weird with the jwt.refresh Middleware. Like @youyi1314 said, it works sometimes, and sometimes it wont. I've followed @kylestev advice with an explicit refresh action and it seems to work fine.

tdhsmith commented 8 years ago

Just to clarify 2 things about the intended behavior:

francoismart commented 8 years ago

To follow up on @tdhsmith would you mind let me know how you can do this feature : " If you need to bypass this, consider simply generating a totally new token instead of utilizing the refreshing mechanism. " without storing credentials on client side ?
in case of mobile app where I would like to never keep the password stored, only maybe user name and jwt token

tdhsmith commented 8 years ago

@francoismart You want to be able to chain refreshes forever, without logging in again?

francoismart commented 8 years ago

yes like outlook app on iOS or whatsapp or Facebook. maybe there's another better way to have the user feel secure in using the app but also connected ? If i need to store credentials and re-login from time to time I can do it. In this cas, what's exactly the concept of refresh token ?

ssi-anik commented 8 years ago

@kylestev Thanks for your approach. It just took 3 hours of my life. Couldn't invalidate the token for log out.

adiachenko commented 8 years ago

I think that refresh middleware is confusing in it's current form. It's only logical that you'll want to apply it on a route that utilizes token authentication functionality to refresh the expired token but in this case request will fail before it'll achieve it's purspose. This kind of invalidates the point of it being the middleware and looks more like a latent behavior of a separate route.

paramadharmika commented 8 years ago

You can use setup a route on your laravel project and use default 'jwt.refresh' middleware on that route. The new token itself will on the authorization header of the response, i.e

These step will be something like below:

  1. Route::post('api/refresh', ['uses' => 'APIAuthenticateController@refresh', 'middleware' => 'jwt.refresh']);
  2. Call the refresh token url: i.e on my local environment look like this http://172.20.10.2:8001/api/refresh?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMsImlzcyI6Imh0dHA6XC9cLzE3Mi4yMC4xMC4yOjgwMDFcL2FwaVwvcmVmcmVzaCIsImlhdCI6MTQ2OTk1MDg0OCwiZXhwIjoxNTAxNDg2ODc0LCJuYmYiOjE0Njk5NTA4NzQsImp0aSI6ImNlMjJmNjkzNzEyYzJhMTMxNjYxYmYwYTE3NDNmZTY1In0.LkvubUzz4YnXu-lmIvvrE-WtlzTzuvRuBThAx0uQafM

The result as shown below

screen shot 2016-07-31 at 3 54 25 pm
diegocaprioli commented 8 years ago

I'm stuck with a Laravel 4 project and I'm having this issue as well, with the version 0.4.3 of this package.

I'm trying the solution suggested by @kylestev on Aug 23, 2015, and I added a refresh-token endpoint:

Route::post('refresh-token', [
            'as'         => 'api.authentication.refresh-token',
            'uses'       => 'Controllers\Api\AuthenticationController@refreshToken',
        ]);
public function refreshToken()
    {
        $token = JWTAuth::getToken();
        Log::info("Trying to refresh token: $token");
        if (!$token) {
            return $this->error('Token NOT provided!', 401);
        }

        Log::info("Refreshing here, but the method throws exception!!??");
        $token = JWTAuth::refresh($token);

        return $this->statusOk(compact('token'));
    }

And I'm getting this dump / exception:

...
[2016-08-26 13:04:58] local.INFO: Trying to refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJzdWIiOiIyODQwNiIsImlzcyI6Imh0dHA6XC9cL2FwaS5hcHAubG9jYWxcL3VzZXJzXC9hdXRoZW50aWNhdGlvbiIsImlhdCI6IjE0NzIyMTM0NzYiLCJleHAiOiIxNDcyMjEzNTM2IiwibmJmIjoiMTQ3MjIxMzQ3NiIsImp0aSI6IjRlZDk5NTY3M2JlZGY1YmRiYjBmNTJhNDM3ZmNiYmMyIn0.OTVjODAyYmJmNTg4YjQzMTc2ZDVmYTE4OGIwMjBkYTVhYjMzNjM0NTU0YTZjMDYwNWUwZjg0NTI1ZGJmOGE5OA [] []
[2016-08-26 13:04:58] local.INFO: Refreshing here, but the method throws exception!!?? [] []
[2016-08-26 13:04:58] local.ERROR: exception 'Tymon\JWTAuth\Exceptions\TokenExpiredException' with message 'Token has expired' in /vagrant/vendor/tymon/jwt-auth/src/Validators/PayloadValidator.php:74
Stack trace:
#0 /vagrant/vendor/tymon/jwt-auth/src/Validators/PayloadValidator.php(32): Tymon\JWTAuth\Validators\PayloadValidator->validateTimestamps(Array)
#1 /vagrant/vendor/tymon/jwt-auth/src/Payload.php(29): Tymon\JWTAuth\Validators\PayloadValidator->check(Array)
#2 /vagrant/vendor/tymon/jwt-auth/src/PayloadFactory.php(68): Tymon\JWTAuth\Payload->__construct(Array, Object(Tymon\JWTAuth\Validators\PayloadValidator), false)
#3 /vagrant/vendor/tymon/jwt-auth/src/JWTManager.php(67): Tymon\JWTAuth\PayloadFactory->make(Array)
#4 /vagrant/vendor/tymon/jwt-auth/src/JWTManager.php(84): Tymon\JWTAuth\JWTManager->decode(Object(Tymon\JWTAuth\Token))
#5 /vagrant/vendor/tymon/jwt-auth/src/JWTAuth.php(130): Tymon\JWTAuth\JWTManager->refresh(Object(Tymon\JWTAuth\Token))
#6 /vagrant/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(208): Tymon\JWTAuth\JWTAuth->refresh(Object(Tymon\JWTAuth\Token))
#7 /vagrant/app/controllers/Api/AuthenticationController.php(132): Illuminate\Support\Facades\Facade::__callStatic('refresh', Array)
#8 /vagrant/app/controllers/Api/AuthenticationController.php(132): Tymon\JWTAuth\Facades\JWTAuth::refresh(Object(Tymon\JWTAuth\Token))
#9 [internal function]: Controllers\Api\AuthenticationController->refreshToken()
...

I'm not sure if I'm doing something wrong here, or this is still an issue on 0.4.3? Anyone can give some help here?

jwt.refresh filter doesn't exists in the 0.4.3 version. At least I can't find the filter anywhere in the source code, and adding it does nothing neither...

unnutz commented 7 years ago

Just found a working solution that allows to use custom middleware that refreshes token along with standard middleware that just authenticates:

namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Exceptions;
use Tymon\JWTAuth\Middleware\BaseMiddleware;

class RefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if ($request->method() != 'OPTIONS') {

            try {

                $token = $this->auth->setRequest($request)->parseToken()->refresh();

                $this->auth->setToken($token); // <-- This one will let request through without blacklist error

            } catch (Exceptions\TokenExpiredException $e) {

                return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);

            } catch (Exceptions\JWTException $e) {

                return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);

            }

            // send the refreshed token back to the client
            $response->headers->set('X-Token', $token);

        }

        return $response;
    }
}
earnaway commented 7 years ago

@TheGhoul21 - just wondering where you placed the JWTAuth::setRequest($request); for phpunit? I'm stuck on that now and can't figure out the best place for it inside my tests. Thanks!

francisrod01 commented 7 years ago

@unnutz I use the same implementation bug the token always return TokenExpiredException throw.

My doubt is: What else could I do to update an expired token?

Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvYXBpL2xvZ2luIiwiaWF0IjoxNDkzNzQ3MzQ5LCJleHAiOjE0OTQ5NTY5NDksIm5iZiI6MTQ5Mzc0NzM0OSwianRpIjoiZnZUZXV0clByNDJjOUFoMyIsInN1YiI6MSwidXNlciI6eyJpZCI6MSwibmFtZSI6IkNPVFZWdU1ldnMiLCJlbWFpbCI6InpzMUx0MVJHOEdAZ21haWwuY29tIiwibGFzdF9sb2dnZWRfaW5fYXQiOm51bGx9fQ.b9u3YpMr_AqZvjesRmOsQfVgw38V6FZxweF4osoEWC0
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class CustomRefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Check for token requested.
        if (!JWTAuth::getToken()) {
            if ($request->acceptsJson() || $request->expectsJson()) {
                return JsonResponse::create([
                    'error' => \Lang::get('auth.token_not_provided')
                ], Response::HTTP_UNAUTHORIZED);
            } else
                throw new BadRequestHttpException('Token not provided');
        }

        try {

            // Refresh the token requested.
            $newToken = JWTAuth::refresh();

        } catch (TokenBlacklistedException $e) {
            return JsonResponse::create([
                'error' => \Lang::get('auth.token_blacklisted')
            ], Response::HTTP_UNAUTHORIZED);
        } catch (TokenExpiredException $e) {
            return JsonResponse::create([
                'error' => \Lang::get('auth.token_expired')
            ], 419);
        } catch (JWTException $e) {
            if ($request->acceptsJson() || $request->expectsJson()) {
                return JsonResponse::create([
                    'error' => \Lang::get('auth.token_absent')
                ], Response::HTTP_UNAUTHORIZED);
            } else
                throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), $e, $e->getCode());
        }

        // Defined response header.
        $response = $next($request);

        // Send the refreshed token back to the client.
        return $this->setAuthenticationHeader($response, $newToken);
    }
}
yadurvt commented 5 years ago

Hi. I use this middleware for all the api requests. Its working. New token is passed to the front end and old token from localstorage is updated before next request.

public function handle($request, Closure $next)
    {
        $success_status = 200;
        try{
            $user = JWTAuth::toUser($request->header('Authorization'));
        }catch (JWTException $e) {
            if($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException || $e instanceof \Tymon\JWTAuth\Exceptions\TokenBlacklistedException) {
                try {
                    $new_token  = JWTAuth::refresh($request->header('Authorization'));
                    $status     = 200;
                    // set new token in to the request header
                    $request->headers->set('Authorization',$new_token);
                    $response          = $next($request);
                    $original          = $response->getOriginalContent();
                    $original['token'] = $new_token;
                    // set response - token as a common parameter
                    $response->setContent(json_encode($original));
                    return $response;
                } catch (TokenExpiredException $e) {
                    $status     = 401;
                    $message    = 'Please Login Again. Your Session Timed Out';
                    return response()->json(compact('status','message'),$success_status);
                }
                catch (JWTException $e) {
                    $status     = 401;
                    $message    = 'Please Login Again. Refresh token time expired';
                    return response()->json(compact('status','message'),$success_status);
                }          
            }else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) {
                $status     = 401;
                $message    = 'This token is invalid. Please Login';
                return response()->json(compact('status','message'),$success_status);
            }else{
                $status     = 404;
                $message    = 'Token is required';
                return response()->json(compact('status','message'),$success_status);
                //return response()->json(['error'=>'Token is required']);
            }
        }
       return $next($request);
    }
mahkassem commented 4 years ago

I have a solution that I use, it is very simple but I don't know if it is secure or not (need your opinions), I simple use the middleware inside AuthController and add 'refresh' route in exceptions. image

kamleshwebtech commented 3 years ago

I have a solution that I use, it is very simple but I don't know if it is secure or not (need your opinions), I simple use the middleware inside AuthController and add 'refresh' route in exceptions. image

@mahkassem could you please share authcontroller controller and middleware, you have? Thanks a lot.

mahkassem commented 3 years ago

I have a solution that I use, it is very simple but I don't know if it is secure or not (need your opinions), I simple use the middleware inside AuthController and add 'refresh' route in exceptions. image

@mahkassem could you please share authcontroller controller and middleware, you have? Thanks a lot.

I use the defaults