passport: Refresh tokens can't be used after access token is expired and pruned?

My app has an access token (4 hour expiry) and refresh token (1 year expiry). Once the access token expires I see it is removed from the oauth_access_tokens table. My refresh token is still in the oauth_refresh_tokens table since it hasn’t expired (and revoked=0).

When my app makes the refresh_token call to oauth/token, instead of getting a new access token and refresh token, it gets:

{
    "error": "invalid_request",
    "message": "The refresh token is invalid.",
    "hint": "Token has been revoked"
}

After this response, I can still see my refresh token is in the oauth_refresh_tokens table with revoked=0 and the expiry date is still about a year out.

The RFC seems to indicate refresh tokens are the way to go since they can be used to get a new access token after a previous access token expires, but that isn’t the behavior I’m seeing.

As a sanity check, I have verified that I can use the refresh token to get a new access and refresh token pair up until the point that the access token is pruned. If the access token is expired but not yet pruned it still works. It goes sideways once it’s been pruned.

Is this the expected behavior? If so, is there something I can do to use a refresh token after the access token has expired & been pruned?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 20 (17 by maintainers)

Most upvoted comments

I’ll re-open this to take another look at a later time. I feel that this is indeed something that needs further attention.

Okay. I’ll have a look at this soonish. Thanks for helping out @Sephster! 😃

Thanks Andrew for your attention. Really appreciate your response. Look forward to any updates. All the best to you and your teams and families.

Regards,

Greg

On 18 Jul 2019, at 7:13 pm, Andrew Millington notifications@github.com wrote:

This is an ideal case for the token revocation RFC. The problem with the original OAuth2 RFC is that it described lots of different ways to obtain an access token but never how to revoke one. It was kind of left up to the implementer.

This effectively meant that you could login to a system but you couldn’t reliably log out. The token revocation RFC resolves this. It would provide an end point that allows you to revoke a refresh token or access token. When you revoke a refresh token, it should also revoke all associated access tokens.

We have an open issue and pull request to look at implementing this RFC in League’s OAuth2 server. I believe this is probably the best route to go down to resolve your issues.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

I am facing the same as @TimWolla discovered.

I insist that it is a Passport concern. When the user clicks revoke on the Authorized Clients, it is not revoking the access_token it is revoking the client (and its application) which should result in: 1. The access_token for that user issued by that client is not valid anymore AND 2. Of course, as the user revoked the client application, there should not exist any way to obtain an acccess_token again, which implies the refresh_token should get invalid as well.

Normally, any application will try to use a refresh token when lost access with a regular access_token and, if the user revoked that application, that application should not be able to obtain a new one again, without passing through the user permission or credentials again (code or password grants).

Imagine using Facebook to login to InsecureSite. dot com, then you realize it is insecure so you revoke access, but magically InsecureSite gains access again after a few minutes just by refreshing the token and you do not even realize that!

This is an important concern in fact.

I hope I was clear to illustrate the current issue.

PS: In Passport, you can make a refresh_token to expires, of course, usually much time later than the access_token.

@driesvints I was the person that changed this in #391 as part of another fix (deleted entries are valid for ever). Please consider the following:

The GET /oauth/tokens route lists the “approved applications” for a user with DELETE /oauth/tokens/{token_id} revoking a token.

The AuthorizedAccessTokenController::forUser method only lists non-revoked tokens.

Now the user revokes this token, but this does not revoke the refresh token. The application will disappear from the list and the user rightly assumes that it now longer has access. But the application can create itself a new token by using the refresh token.

I see two solutions to this:

  • When revoking the token in AuthorizedAccessTokenController::destroy also revoke the refresh token (and ideally any other token of the same application). This way the user can revoke the token including the refresh token while the application can opt to revoke single tokens, without necessarily killing the refresh token. Show the token in the list when either the normal token or it’s refresh token is non-revoked (the user needs to be able to revoke the refresh token even when the normal token is already dead).
  • Make the refresh tokens part of the list in /oauth/tokens and support revoking them with the /oauth/tokens/{token_id}.

Should I create a new issue for this?

I can’t say at the moment. As I do this in my spare time, it really depends on how much work/life stuff I have coming up. I will prioritise this though now version 8 is out.

This is an ideal case for the token revocation RFC. The problem with the original OAuth2 RFC is that it described lots of different ways to obtain an access token but never how to revoke one. It was kind of left up to the implementer.

This effectively meant that you could login to a system but you couldn’t reliably log out. The token revocation RFC resolves this. It would provide an end point that allows you to revoke a refresh token or access token. When you revoke a refresh token, it should also revoke all associated access tokens.

We have an open issue and pull request to look at implementing this RFC in League’s OAuth2 server. I believe this is probably the best route to go down to resolve your issues.

Sorry @driesvints, yes we are. I just checked the code and we do it when we respond to an access token request:

// Expire old tokens
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);