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:
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?
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.
The Refresh token has a sliding window that is valid for 14 days and refresh token's validity is for 90 days.
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.
The refresh token is used indefinitely, unless revoked by the user or Salesforce admin.
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)
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)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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With