Merge pull request #789 from rithujohn191/token-revocation-proposal
Documentation/proposals: Add a proposal for refresh token revocation.
This commit is contained in:
commit
36883d0bbf
1 changed files with 82 additions and 0 deletions
82
Documentation/proposals/token-revocation.md
Normal file
82
Documentation/proposals/token-revocation.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# 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
|
||||
|
||||
1. Currently refresh tokens are not associated with the user. Need a new "session object" for this.
|
||||
2. Need an API to list refresh tokens based on the UserID.
|
||||
3. We need a way for users to login to dex and revoke a client.
|
||||
4. 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.
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
1. Client requests a refresh token:
|
||||
Try to retrieve the `OfflineSession` object for the User with the given `UserID + 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 new `RefreshToken` pointer.
|
||||
* Delete the old refresh token in storage.
|
||||
|
||||
* 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
|
||||
the `Refresh` list.
|
||||
|
||||
2. 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 the `RefreshToken` obj gets updated with a new nonce. We do not need
|
||||
any additional checks in the OfflineSession objects as the `RefreshToken` pointers still remain intact.
|
||||
|
||||
3. 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 the `RefreshToken` (handle this in storage)
|
||||
and its pointer value in `Refresh`. Clean up the OfflineSession object.
|
||||
* If there is no refresh token found, handle error case.
|
||||
|
||||
NOTE: To avoid race conditions between “requesting a refresh token” and “revoking a refresh token”, use
|
||||
locking mechanism when updating an `OfflineSession` object.
|
Reference in a new issue