Closed mrgodhani closed 6 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.
what version of the package/laravel are you using?
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
@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
Any update on this ? It's really annoying. I had to separate out my all get requests to another route group without jwt.refresh.
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.
Hi @mrgodhani, this code works for me:
$token = JWTAuth::getToken();
$newToken = JWTAuth::refresh($token);
@jopereyral I was referring to middleware. Using middleware (jwt.refresh) it won't refresh expiredtoken.
@mrgodhani The middleware is working as intended:
it just blacklists token after one request
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:
/api/auth/refresh_token
which has the jwt.refresh
middleware applied but not the jwt.auth
middleware.refresh_token
endpoint if they have a request that fails because a token has expired.@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"
}
@shivekkhurana are you using the latest version of this package?
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 ?
@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.
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.
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
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.
Just to clarify 2 things about the intended behavior:
jwt.auth
and jwt.refresh
middlewares are not designed to work together on a single route. Auth is implemented as a 'before' and refresh is implemented as an 'after', so auth will reject all expired tokens, including those that are still refreshable, before they get a chance to be refreshed. Also because it's an after, the refresh middleware will not work well for a single-use token flow because it will always run the server action $next
before checking token validity. If you want the default 'refresh-as-needed' flow, use @kylestev's approach. If you want single-token, you will need to create your own middleware.refresh_ttl
sets a maximum limit from the original token's time of issue. You cannot chain refreshes indefinitely. (Depending on your setup, that could be considered bad security practice. An attacker could stay logged in for as long as they wanted without ever knowing the user's credentials.) If you need to bypass this, consider simply generating a totally new token instead of utilizing the refreshing mechanism.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
@francoismart You want to be able to chain refreshes forever, without logging in again?
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 ?
@kylestev Thanks for your approach. It just took 3 hours of my life. Couldn't invalidate the token for log out.
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.
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:
Route::post('api/refresh', ['uses' => 'APIAuthenticateController@refresh', 'middleware' => 'jwt.refresh']);
http://172.20.10.2:8001/api/refresh?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMsImlzcyI6Imh0dHA6XC9cLzE3Mi4yMC4xMC4yOjgwMDFcL2FwaVwvcmVmcmVzaCIsImlhdCI6MTQ2OTk1MDg0OCwiZXhwIjoxNTAxNDg2ODc0LCJuYmYiOjE0Njk5NTA4NzQsImp0aSI6ImNlMjJmNjkzNzEyYzJhMTMxNjYxYmYwYTE3NDNmZTY1In0.LkvubUzz4YnXu-lmIvvrE-WtlzTzuvRuBThAx0uQafM
The result as shown below
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...
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;
}
}
@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!
@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);
}
}
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);
}
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.
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.
@mahkassem could you please share authcontroller controller and middleware, you have? Thanks a lot.
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.
@mahkassem could you please share authcontroller controller and middleware, you have? Thanks a lot.
I use the defaults
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.