3.8 KiB
Proposal: design for revoking refresh tokens.
Refresh tokens are issued to the client by the authorization server and are used to request a new access token when the current access token becomes invalid or expires. It is a common usecase for the end users to revoke client access to their identity. This proposal defines the changes needed in Dex v2 to support refresh token revocation.
Motivation
- Currently refresh tokens are not associated with the user. Need a new "session object" for this.
- Need an API to list refresh tokens based on the UserID.
- We need a way for users to login to dex and revoke a client.
- Limit the number refresh tokens for each user-client pair to 1.
Details
Currently in Dex when an end user successfully logs in via a connector and has the OfflineAccess scope set to true, a refresh token is created and stored in the backing datastore. There is no association between the end user and the refresh token. Hence if we want to support the functionality of users being able to revoke refresh tokens, the first step is to have a structure in place that allows us retrieve a list of refresh tokens depending on the authenticated user.
// Reference object for RefreshToken containing only metadata.
type RefreshTokenRef struct {
// ID of the RefreshToken
ID string
CreatedAt time.Time
LastUsed time.Time
}
// Session objects pertaining to users with refresh tokens.
//
// Will have to handle garbage collection i.e. if no refresh token exists for a user,
// this object must be cleaned up.
type OfflineSession struct {
// UserID of an end user who has logged in to the server.
UserID string
// The ID of the connector used to login the user.
ConnID string
// List of pointers to RefreshTokens issued for SessionID
Refresh []*RefreshTokenRef
}
// Retrieve OfflineSession obj for given userId and connID
func getOfflineSession (userId string, connID string)
Changes in Dex CodeFlows
-
Client requests a refresh token: Try to retrieve the
OfflineSession
object for the User with the givenUserID + ConnID
. This leads to two possibilities:-
Object exists: This means a Refresh token already exists for the user. Update the existing
OffilineSession
object with the newly received token as follows:- CreateRefresh() will create a new
RefreshToken
obj in the storage. - Update the
Refresh
list with the newRefreshToken
pointer. - Delete the old refresh token in storage.
- CreateRefresh() will create a new
-
No object found: This implies that this will be the first refresh token for the user.
- CreateRefresh() will create a new
RefreshToken
obj in the storage. - Create an OfflineSession for the user and add the new
RefreshToken
pointer to theRefresh
list.
- CreateRefresh() will create a new
-
-
Refresh token rotation: There will be no change to this codeflow. When the client refreshes a refresh token, the
TokenID
still remains intact and only theRefreshToken
obj gets updated with a new nonce. We do not need any additional checks in the OfflineSession objects as theRefreshToken
pointers still remain intact. -
User revokes a refresh token (New functionality): A user that has been authenticated externally will have the ability to revoke their refresh tokens. Please note that Dex's API does not perform the authentication, this will have to be done by an external app. Steps involved:
- Get
OfflineSession
obj with given UserID + ConnID. - If a refresh token exists in
Refresh
, delete theRefreshToken
(handle this in storage) and its pointer value inRefresh
. Clean up the OfflineSession object. - If there is no refresh token found, handle error case.
- Get
NOTE: To avoid race conditions between “requesting a refresh token” and “revoking a refresh token”, use
locking mechanism when updating an OfflineSession
object.