Personal Access Tokens (aka API Keys v2)
The current implementation for API keys has a number of issues
API keys are stored in plaintext
Only one API key can be generated at a time
Revoking the API key invalidates all sessions authenticated with the API key
Computing the signature required to use them is complicated
API keys allow access to all account functions, which may be unnecessary in certain contexts
Some of these issues have been resolved in the designs for Synapse OAuth 2 authorization mechanisms. A new design for API keys would allow us to take advantage of these solutions, and deprecate API keys.
Inspiration: GitHub Personal Access Tokens
Use Cases
The main use case for user-generated refresh tokens differ little from API keys:
The ability to authenticate requests from command line clients without a password
Both real-time sessions and automated jobs.
Resolving the issues stated above provides additional flexibility, and provides users with additional protections, with the trade-off being increased susceptibility to replay attacks (which should be prevented by https).
This sample scenario identified by @Jordan Kiang (Unlicensed) is useful as it helps determine additional requirements:
I have an external S3 bucket configured as a storage location
I have instrumented an S3 event notification in AWS such that new objects trigger a Lambda function
The Lambda function uses the Python synapse client to add an external S3 file handle to Synapse for each new object
I want this to be a relatively "set it and forget it" sequence, not involving any human interaction
Requirements and Design Decisions
A user must be able to issue multiple access tokens
A user must be able to view metadata about their active tokens, e.g. scope, a custom name/identifier
A user must be able to revoke an individual access token
Generated access tokens must use scopes as defined in the OAuth 2 implementation
Generated access tokens should only expire if unused for 180 days or manually revoked
A client application should be able to access a token from a read-only key store and multiple, parallel client processes should be able to use the token without being concerned about concurrency issues.
Generated access tokens should be bearer tokens
A password/session token must not be required to use an access token
Functionality identical to OAuth access tokens is preferred
It should be impossible for any actor to determine the token other than the issuer at creation time (i.e. store a hash).
Services
Access tokens cannot be modified after they have been created.
Service | Request Parameters | Response Body | Notes |
---|---|---|---|
POST /auth/v1/personalAccessToken | Body: AccessTokenGenerationRequest {
} | AccessTokenGenerationResponse {
} | Generates a token that users can copy and paste into the command line client. |
GET /auth/v1/personalAccessToken | None | Paginated list of AccessTokenRecord: {
} | Retrieves a paginated list of the user’s generated access tokens. Tokens that are active or expired will appear. Tokens that have been revoked (deleted) will not appear. |
DELETE /auth/v1/personalAccessToken/{id} | Path param: id: the id of the token to delete | None | Revokes the token if it’s a valid access token. |
How to use a personal access token
The personal access token can be used by putting the token in the Authorization header with the ‘Bearer’ keyword preceding the token (Authorization: Bearer <token>
)
Open Questions
Which of these services be accessible via scoped access tokens?
I think by default, creating/deleting should not be accessible at all. If a use case comes up, then these can be accessible via the
authorize
scope. Viewing the list of tokens can be done with theview
scope.
What should the token look like? Some options
Non-expiring, signed JWT (containing an integer token ID and a user ID)
Opaque token
With or without additional userId header?
This is partially an implementation question. The ability to revoke tokens requires storing tokens or token identifiers in persistent storage. Performance is critical because a database lookup will be done for every authenticated request made using one of these tokens.