Document toolboxDocument toolbox

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:

  1. I have an external S3 bucket configured as a storage location

  2. I have instrumented an S3 event notification in AWS such that new objects trigger a Lambda function

  3. The Lambda function uses the Python synapse client to add an external S3 file handle to Synapse for each new object

  4. 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

Service

Request Parameters

Response Body

Notes

POST /auth/v1/personalAccessToken

Body:

AccessTokenGenerationRequest {

name: string (a unique-to-the-user, human-readable name. if unspecified, a UUID will be generated)

scope: Array<OAuthScope> (scopes granted by the token)

claims: OIDCClaimsRequest (claims granted by the token)

}

AccessTokenGenerationResponse {

token: string (A signed JWT access token)

}

Generates a token that users can copy and paste into the command line client.

GET /auth/v1/personalAccessToken

None

Paginated list of AccessTokenRecord: {

id: unique ID of the access token

scope: Array<OAuthScope> scope of the token

claims: claims granted by the token

name: string, human readable name

lastUsed: the last time an access token was used

createdOn: the date/time the access token was created

state: enum (ACTIVE or EXPIRED)

}

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 the view 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.