On my route I'm using the jwt.refresh middleware.
I set the ttl to 1 to make sure refresh was working correctly, and encountered an issue.
When I try to get the current user (JWTAuth::parseToken() -> toUser()) in my controller, because the token hasn't yet been refreshed the token is invalid and it throws an exception.
Another issue I encountered here was that instead of it throwing an exception that I could catch, it seems to throw an exception I can't catch - not sure why. But my error log shows: local.ERROR: exception 'TymonJWTAuthExceptionsTokenExpiredException' with message 'Token has expired
Attempts to catch TokenExpiredException don't work.
This issue occurs in 0.5.3 and in the latest dev version.
My workaround has been to modify RefreshToken so that the token is refresh before calling $next. Not sure whether this is a correct fix, or whether this might have an unwanted impact elsewhere.
Yes I believe you are saying:
"->refresh()" should not just blindly invalidate a token and it should check to make sure that it needs a refresh before"
This would fix the main issue with using ->refresh() in the current supplied middleware.
@codeorganic The issue here is that the token is already invalid, but can be refreshed. But because of the order the middleware is run in, the expired token is used to get the user before it's refreshed.
As it's an expired token, getting the user isn't possible.
As for the non caught exception, make sure you're trying to catch \Tymon\JWTAuth\Exceptions\TokenExpiredException and not Tymon\JWTAuth\Exceptions\TokenExpiredException (mind the backslash at the beginning)
@mattmcdonald-uk just to clarify, are you applying both the middlewares to the same route, or just RefreshToken ?
@tymondesigns I'm just applying the refresh middleware. I originally applied both, but that didn't work as the first middleware was failing because the token was invalid (pre-refresh). Same problem the parseToken method is having.
@osteel Yes - spot on with the missing backslash (docs here also missing it: https://github.com/tymondesigns/jwt-auth/wiki/Authentication but perhaps the presume you've declared Tymon above?)
@mattmcdonald-uk You basically have the same "problem" as the one I'm describing here. The token is expired but still _refreshable_, but when using both middlewares on the same route, the GetUserFromToken one prevents the RefreshToken one to be run through as the former throws an exception.
Like I'm saying in the other issue, I'm not sure whether it's actually a "bug" or if we are expected to catch the TokenExpiredException and perform the checks/refresh manually.
It feels like we _should_ be able to use both at the same time seamlessly, but maybe that's just not the case.
Any thoughts about a recommended flow are warmly welcome :)
@tymondesigns oh and by the way, I confirm that even using the RefreshToken middleware alone on a route, if the token is expired but still refreshable, it doesn't get resfreshed (tested with ttl of 1 minute and refresh_ttl of 2 weeks to make sure).
@osteel But I'm just using a single middleware. I originally presumed both middleware should be used together - but then realised they're probably an either/or. Happy for them to be either/or - BUT even when using just the RefreshToken middleware, there's a problem because it's not refreshing the token until after your controller logic has run.
So the flow that I presume should be there (and the way I've modified it to work for me for now) is to re-arrange the RefreshToken middleware so that $response = $next($request); is called after the token has been refreshed.
I imagine if you can set your middleware to run in the order of RefreshToken first, and have the token refreshed first, then the second middleware would work too.
@mattmcdonald-uk yeah I agree, as confirmed in my previous comment ;)
The thing that would make me raise an eyebrow with the flow you describe tho (when using both middlewares), is if you get the RefreshToken one to refresh the token _before_ the request gets through the GetUserFromToken one, why not just get rid of refresh_ttl and simply set ttl to 2 weeks straight away? It seems to me it would boil down to the same thing?
@tymondesigns As for using RefreshToken alone, what is it actually expected to do in case of an expired token that can still be refreshed? Let the server response with a 401 status code but add an Authorization header to it with a refreshed token so the client can try again the request with the new token?
I still have this feeling there is something I don't entirely grok :)
Ok, here goes..
Firstly, let me say that I never intended the middlewares to be used on the same route, and instead I would advocate a separate endpoint for each purpose. i.e. One for retreiving the authenticated user and one for simply refreshing tokens.
The Client would then marshall the token refreshing process. For example within an AngularJS SPA, you could implement a transparent token refreshing flow using response interceptors.
When a 401 response is returned (due to token expiration) then you would make a subsequent request to a refresh endpoint, to replace the expired token.
The initial use case for the RefreshToken middleware was to provide a single use token flow, meaning a new token is returned for every authenticated request, but I think I will need to think about what I'm going to offer out of the box for the next version, as it can be confusing
I will also look at the possible bug you guys raised with the refresh_ttl aswell, and hopefully get that resolved asap
Hi @tymondesigns, thanks for your answers. A few comments:
Firstly, let me say that I never intended the middlewares to be used on the same route
Hmm ok, I think that would make sense tho? I can easily imagine using both of them, GetUserFromToken to control the validity of the token _before_ the request is processed, and then RefreshToken to pass a refreshed token to the response, in order to use a single-use token flow.
That's actually what I'm doing.
I would advocate a separate endpoint for each purpose
This confuses me a tad. I can't see how 2 API endpoints could perform what the middlewares do. We want to control the validity of the token before processing the request. How I understand what you're saying is that we should hit an endpoint first to control the validity, wait for the response and then hit the endpoint we originally intended to request?
Same goes for an endpoint that would refresh the token. I understand now there should be one allowing to refresh it when it's invalid (ttl has passed) but still refreshable (< refresh_ttl), but I don't see how using an endpoint would work for the single-token flow.
This is how I do it at the moment:
GetUserFromToken middleware controls the validity of the token before the request is processedRefreshToken middleware passes a refreshed token to the responseGetUserFromToken raises an exception (or triggers an event if you stick to the original behaviour) that is caught by a handler, and the handler checks if the token is still refreshable401 Unauthorized response but with a refreshed token in the Authorization headerrefresh_ttl), the handler simply returns a 401 Unauthorized response without any tokenI'm rather satisfied by this flow, but if anyone sees anything weird with it please don't hesitate to comment.
Sorry for the indigestible doorstop, but I think some clarification would benefit to quite a few people indeed :)
Oh and by the way, failed to mention until now but thanks for your work, that's a very useful package :+1:
That's basically the flow I use, except if a token is refreshable then it gets refreshed and the request is processed as before without returning a 401.
I would agree with @osteel that I'd prefer not to have a separate endpoint for refreshing tokens. My application flow is such that the frontend expects a new token in the header of every response, and replaces its existing saved token each time.
With your explanation @tymondesigns I do now understand why things weren't working the way I'd expected! And can see how with the flow you described the existing code would work perfectly.
But perhaps there's a way to allow both approaches to work?
Either way - great code, really helpful, but the documentation doesn't make the intended flow entirely clear!
Thanks for your feedback guys, I will digest and get back to you when I'm at my home machine :+1:
From what i grasped of the concept it does make sense to me just the way it is!
You make a request with an expired token you should be informed that it's expired and not refreshed automatically.
The middleware is there to increase the security of the token therefore RefreshToken may be a misleading name for it. better say (RenewToken)
A token should be refreshed after the client acknowledgement of it's expiry!
COUPLE OF LINES TAKEN FROM GOOGLE
Token expiration
You should write your code to anticipate the possibility that a granted token might no longer work!!
Hi @osteel Its great explanation. Can you further explain this point i.e.
If not valid, GetUserFromToken raises an exception (or triggers an event if you stick to the original behaviour) that is caught by a handler, and the handler checks if the token is still refreshable
How can we catch exception raised by GetUserFromToken without overriding the middleware itself? Is there a way to hook response of particular middleware?
@waqasiqrar I actually extended the middleware so it throws exceptions instead of triggering events ;)
@osteel I was totally in line with your response as to a preferred flow, but am a bit caught up as to how to then best handle the potential of async requests. I guess what would happen is whichever request came in first with the token that had expired (but had a refresh/renew) available, would then get its refreshed token in the response header, save it on the client, and the other async requests that fired around that same time with the prior token would simply get a token_invalid since the old token would then be blacklisted. I would then want to check to see if the token I had sent along in the invalidated requests, still matches the token saved in the client, if it's a different token, re-fire request. If no, clear out the local token and send user to login.
I could see a problem with refreshing the token on every call due to the async factor as well as whichever request fired first, but didn't end before others started would have a good token, but all others that fired before the first was completed and the new token set, would have an invalid token, and would have to retry the request with the new token, seems like it'd be a waste of requests pretty frequently.
Curious on your thoughts and implementation. Thanks!
closing as lots changed over on develop.. things should be simpler and clearer in the new release
nice
@tymondesigns Do you perhaps have some sample code to use in your authInterceptor when the token expires to refresh it?
Most helpful comment
Ok, here goes..
Firstly, let me say that I never intended the middlewares to be used on the same route, and instead I would advocate a separate endpoint for each purpose. i.e. One for retreiving the authenticated user and one for simply refreshing tokens.
The Client would then marshall the token refreshing process. For example within an AngularJS SPA, you could implement a transparent token refreshing flow using response interceptors.
When a 401 response is returned (due to token expiration) then you would make a subsequent request to a refresh endpoint, to replace the expired token.
The initial use case for the RefreshToken middleware was to provide a single use token flow, meaning a new token is returned for every authenticated request, but I think I will need to think about what I'm going to offer out of the box for the next version, as it can be confusing