Documentation/proposals: Add a proposal for refresh token revocation.
This commit is contained in:
		
							
								
								
									
										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 New Issue
	
	Block a user