Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doorkeeper Refresh Tokens Not Expiring on Reuse

I'm using Doorkeeper 5.2.1 and Ive consulted the Doorkeeper docs on refresh tokens and have read through several GitHub issues and pull requests related to refresh tokens, notably here and here.

From what I gather from these conversations and from reading the spec (and documents and posts referencing the spec, the following statements are true:

  • Refresh tokens are long lived (as opposed to access tokens, which typically expire after a relatively short TTL)
  • An authorization server MAY implement revocation
  • Doorkeeper implements refresh token revocation on some basis (though I'm not clear what, keep reading)

I'm confused, though, based on this pull request, which implements refresh token expiry after an "access token created with that refresh token is successfully used once." How would Doorkeeper know if I've made an API request with that access token successfully? It's my API that works out the authorization logic based on that access token. Doorkeeper is agnostic to my API requests being successful or not. However, I've interpreted that to mean that if the resource_owner_authenticator block returns a user, that could constitute successful use. Yet I have not found that that expires a refresh token successfully. (See my spec below.)

It also appears from reading this spec file that if you successfully use a refresh token, it revokes the previous refresh tokens, which would make sense.

I'm trying to work all this out in my spec file, though, and I'm running into an issue where it doesn't appear that a refresh token is revoked, even when it's used multiple times, or it ("refresh token A") is reused after the refresh token ("token B") that returns with the access token that refresh token A is used to generate, is also used. My spec file will make this clearer:

describe 'OAuth flow' do
  # ...
  describe 'refresh tokens' do

    # ...
    context 'when attempting reuse of a refresh token' do
      before do
        redirect_uri = 'https://localhost:3002'

        # ivars necessary brecause plaintext tokens/secrets are only available upon creation of the object
        @client = Doorkeeper::Application.create(name: 'foo', uid: 'bar', redirect_uri: redirect_uri, scopes: 'project_index')
        @secret = @client.plaintext_secret
        @grant = Doorkeeper::AccessGrant.create(resource_owner_id: user.id, organization_id: org.id, application_id: @client.id, scopes: 'project_index', expires_in: 600, redirect_uri: redirect_uri)
        @code = @grant.plaintext_token
      end

      it 'revokes the initial refresh token' do
        # Get an initial access token and refresh token
        post "/oauth/token?client_id=#{@client.uid}&client_secret=#{@secret}&code=#{@code}&grant_type=authorization_code&redirect_uri=#{@client.redirect_uri}&scope=project_index"
        expect(response.status).to eq(200)

        # Use refresh token to get a new access token
        previous_refresh_token = JSON.parse(response.body)['refresh_token']

        post "/oauth/token?client_id=#{@client.uid}&client_secret=#{@secret}&code=#{@code}&grant_type=refresh_token&refresh_token=#{previous_refresh_token}&redirect_uri=#{@client.redirect_uri}&scope=project_index"
        expect(response.status).to eq(200)
        json_body = JSON.parse(response.body)
        new_access_token = json_body['access_token']
        new_refresh_token = json_body['refresh_token']

        # Use the new access token successfully
        get "/api/v1/projects?access_token=#{new_access_token}"
        expect(response.status).to eq(200)

        # Use the new refresh token to get yet another access token
        post "/oauth/token?client_id=#{@client.uid}&client_secret=#{@secret}&code=#{@code}&grant_type=refresh_token&refresh_token=#{new_refresh_token}&redirect_uri=#{@client.redirect_uri}&scope=project_index"
        expect(response.status).to eq(200)

        # Attempt reuse of the first refresh token
        post "/oauth/token?client_id=#{@client.uid}&client_secret=#{@secret}&code=#{@code}&grant_type=refresh_token&refresh_token=#{previous_refresh_token}&redirect_uri=#{@client.redirect_uri}&scope=project_index"
        expect(response.status).to eq(400)

        # ^^^ fails, response is 200 and a new access token and refresh token are generated
      end
    end
  end

  # ...
end

This spec fails on the last assertion, suggesting that for a given refresh token, it is not revoked when the access token it is used to generate is used successfully (contrary to what's stated above), or when the refresh token issued with the refreshed access token is used.

My question is, with Doorkeeper, under what circumstances is a refresh token revoked?

like image 509
nickcoxdotme Avatar asked Oct 07 '19 14:10

nickcoxdotme


People also ask

Can refresh tokens be reused?

If a previously used refresh token is used again with the token request, the Authorization Server automatically detects the attempted reuse of the refresh token. As a result, Okta immediately invalidates the most recently issued refresh token and all access tokens issued since the user authenticated.

Does refresh token ever expire?

The Refresh token has a sliding window that is valid for 14 days and refresh token's validity is for 90 days.

How many times can a refresh token be used?

A Refresh Token is valid for 60 days and can be used to obtain a new Access Token and Refresh Token only once. If the Access Token and Refresh Token are not refreshed within 60 days, the user will need to be re-authorized.

How long do Salesforce refresh tokens last?

The refresh token is used indefinitely, unless revoked by the user or Salesforce admin.


1 Answers

The doorkepeer implement a few flows where the issued token whould be revoked. (I doubt I described all of them, I just remember of those)

  • By willingly making a request to revoke the given token.
  • By completing the refresh token flow, which will revoke the refreshed token. (unless the third flow is enabled)
  • By successfully completing an authentication while having the refresh token flow enabled and by having the column previous_refresh_token on your oauth_access_tokens table. This column is automatically populated by doorkeeper during the refresh token flow by the current token being refreshed (becoming than the previous token). (this flow has to be enabled on the doorkeeper configuration)
  • Revoking a grant token, which is used as a pre auth for the other flows. The grant token is revoked as soon as it is used to get another token (such as client or access tokens)
  • On the client credentials flow, by requesting a new token with the reuse access token configuration disabled

Based on all of that I have to say, you're assumption is completly right. The spec you wrote should have revoked the token, which led me to think if that wasn't a bug on doorkeeper. Without seeing your doorkeeper configuration I would be pretty much guessing, but, I found this pull request #1341 from the version 5.3.0 that is suposed to fix a bug on the revocation of refresh tokens when the hash_token_secrets is enabled. If you have hash_token_secrets enabled try disabling it or updating the doorkeeper. If none of those is possible try making a monkey patch (as a last resource) and see if that fixes your problem.

like image 55
Felipe Sadoyama Avatar answered Nov 10 '22 04:11

Felipe Sadoyama