Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents
maxLevel2

Review Changes

For those viewing this document after the initial review meeting, here are the core changes made since then:

  • Added detail to workflow use case

    • Sequence diagrams

  • Removed CLI use case and related requirements

    • Requirements for workflows use case is a subset of the CLI requirements. Let’s tackle workflows first and talk about CLI later.

  • Renamed endpoints for auditing tokens from /oauth2/permissions to /oauth2/grantedClients

Overview and Selected Use

...

Case

This document will outline the design and implementation of OAuth 2.0 refresh tokens and token revocation in Synapse.

...

Token Revocation:

Jira Legacy
serverSystem JIRA
serverIdba6fb084-9827-3160-8067-8ac7470f78b2
keyPLFM-6120

Use Case: Workflows

Users are interested in running workflows (which require access to a user’s Synapse resources) on workflow engines operated by a third party. Because the workflow engine is operated by a third party, it is unacceptable for a user to authorize access using their username and password, or their API key. The short-lived OAuth access tokens currently in Synapse are not sufficient for this use case either, because a submission to a workflow engine may sit in a queue and/or execute for a duration exceeding the lifetime of an access token (currently 24 hours).

These issues can be overcome by issuing a refresh token to the workflow engine.

Refresh Tokens

Refresh tokens would allow a 3rd party client to request a new access token from Synapse. This enables OAuth clients to have long-lived access to a user’s identity and resources. Refresh tokens are an optional component defined in the OAuth 2.0 specification (https://tools.ietf.org/html/rfc6749#section-1.5).

Use Cases for Refresh Tokens

...

Support for Workflows. Workflows that act on behalf of a user may have a long queue of jobs. If the duration of those jobs exceeds the duration of an access token (currently 24 hours), then the job will fail. A workflow triggered by a Synapse OAuth client could utilize a refresh token to maintain authorization beyond the current duration of an access token.

...

Info

Details about how evaluations and workflows could interact are intended as examples and are not the focus of this document

In this example scenario, a Synapse Evaluation queue could be associated an OAuth client and a running instance of a workflow engine. The workflow engine has access to the OAuth client credentials.

...

When a user creates a Submission and submits it to the Evaluation, Synapse will submit the job to the workflow engine with a refresh token intended to be used for the duration of the job. Synapse may revoke the token once the job is completed.

...

Note that in this example, there is no authorization/consent flow. This is because consent to third party access is granted by submitting a job to the evaluation queue. There may be other scenarios involving workflows that do not utilize evaluations, and a typical authorization code flow, including granting consent, may be used

Token Revocation

Because refresh tokens allow a user to issue long-lived access to a 3rd party client, we should allow users and clients to revoke this access. This gives a user more control over their data, and additional services allow a user to audit their granted permissions so they may re-evaluate the services with access to their data. In cases where a client no longer needs access to a Synapse user’s resources, they may revoke the token in order to prevent future unauthorized access.

User-centric token revocation is not defined in any of the OAuth Specifications. The OIDC Specification § 16.18 simply suggests it: ““…The

The Authorization Server SHOULD provide a mechanism for the End-User to revoke Access Tokens and Refresh Tokens granted to a Client.

...

Thus the design for this feature is influenced by other services, such as those showcased by Okta/OAuth.com on their page Listing Authorizationshttps://www.oauth.com/oauth2-servers/listing-authorizations/ .

Use Cases for user-centric token revocation

  • A An user may no longer trust or need to use an OAuth client that has been granted access to their Synapse identity and resources

  • A user may no longer trust an OAuth client that has been granted access to their Synapse identity and resources

Client-centric token revocation is defined in RFC 7009https://tools.ietf.org/html/rfc7009. From the RFC:

From an end-user's perspective, OAuth is often used to log into a certain site or application. This revocation mechanism allows a client to invalidate its tokens if the end-user logs out, changes identity, or uninstalls the respective application. Notifying the authorization server that the token is no longer needed allows the authorization server to clean up data associated with that token (e.g., session data) and the underlying authorization grant. This behavior prevents a situation in which there is still a valid authorization grant for a particular client of which the end-user is not aware. This way, token revocation prevents abuse of abandoned tokens and facilitates a better end-user experience since invalidated authorization grants will no longer turn up in a list of authorization grants the authorization server might present to the end-user.

...

Use Case for client-centric token revocation in context

  • Resource access is no longer needed by the client. As an example, a workflow engine job executed by an OAuth client may no longer require access to a user’s Synapse account resources at the conclusion of a job. They may revoke the token to ensure it is no longer valid.

Token Revocation

...

Behavior

Currently, only short-lived access tokens are minted in Synapse, and these tokens cannot be revoked because they are not stored in the database.

Since we will be issuing long-lived refresh tokens, we will need a mechanism to revoke refresh tokens. While not necessary, it would be ideal to also revoke the access tokens themselves. (RFC 7009 § 2)

To accomplish this, we can store refresh tokens in the database, which can be revoked by a user or client with a REST API call. Once the refresh token is revoked, it may no longer be used to generate access tokens.

By linking an access tokens token to its associated refresh token, we are able to invalidate access tokens without storing them in the database.

Linking Access Tokens to Refresh Tokens

Access tokens are JWTs. To link an , so they can securely transmit information. Thus we can link any particular access token to a the refresh token , we can simply add a claim with a that permits it to be issued by adding a claim to the access token that contains its corresponding refresh token ID. The JWT specification § 4.2 suggests we use a namespace for this claim, (e.g. Auth0 recommends a URL like https://synapse.org/refresh_token_id or https://sagebionetworks.org/refresh_token_id, but we should be able to use a reverse domain name like org.sagebionetworks.repo.model.oauth.claims.refresh_token_id). As a side note, I think we are already in violation of this specification, since we currently use nonstandard, non-namespaced claims such as orcid, is_certified, etc. We should determine if we should get back “in-spec” and add namespaces to the existing claims (breaking API change).

...

This section will identify a three new object objects used in the REST API, three seven new endpoints, and an extended implementation for an existing endpoint.

New objects

There are two three new objects proposed in this document.

...

OAuthClientAuthorization

This object can be used to show the a user the OAuth clients that have access to the requesting user’s resources and identity. Using this information, the user can identify the client that has access, the amount of access that the client has (via scopes), how long the client has had access, and how recently the client has accessed that user’s resources by requesting a new access token.

Field

Type

...

Description

client

...

...

Client information that can be displayed to the end user

authorizedOn

date-time

The time when access was first granted (i.e. the issue date of the oldest active refresh token)

lastUsed

date-time

...

The most recent time a refresh token was used to issue a new access token

OAuthRefreshTokenInformation

This object captures information about an active refresh token, intended to be seen by the user whose resources can be accessed by the token. Note that the token itself is not shown.

Field

Type

Description

tokenId

integer

Unique ID of the token

clientId

integer

Unique ID of the client that possesses this token

name

string

A human-readable identifier for the token. We may initially set this to a string of random words. The user is able to overwrite this field (e.g to identify the machine on which this token lives)

scopes

Array<OAuthScope>

The scopes that the client can request using

...

this refresh token

authorizedOn

date-time

...

The time when

...

this token was first

...

issued

lastUsed

date-time

...

The most recent time

...

this refresh token was used to issue a new access token

modifiedOn

date-time

The last time this token’s metadata (i.e. name) was updated.

etag

string

For OCC

OAuthTokenRevocationRequest

This object is used when a client makes a request to revoke a refresh/access token. It is defined by RFC 7009 § 2.1.

Field

Type

...

Description

token

...

string

The token to revoke

token_type_hint

...

enum

The type of token to revoke (must be access_token

...

or refresh_token

...

)

New API Endpoints

Three This section outlines new endpoints and an extension of implementation for one existing endpoint are proposedthe behavior of each.

As a note, all new methods that can be accessed on behalf of a user (i.e. with a session token or access token) are organized under /oauth2/audit. All client actions (that require client credentials) are organized under /oauth2/token.

Viewing applications that have OAuth access to a user’s account

Endpoint: GET /oauth2/audit/grantedClients
Request body: none
Return body: PaginatedList<OAuthClientAuthorization>
Returns a paginated list of the clients and permissions that the user has granted. Allows a user to audit which parties have access to their resources.

Viewing tokens for an application that has OAuth access to a user’s account

Endpoint: GET /oauth2/audit/grantedClients/:client_id/tokens
Path Parameter: client_id: returned tokens will be associated with this OAuth2 client
Request body: none
Return body: PaginatedList<OAuthGrantedPermission>PaginatedList<OAuthRefreshTokenInformation>
Returns a paginated list of the clients and permissions that the user has granted. Allows a user to audit which parties have access to their resources.

User revocation of a client’s access

Endpoint: POST /oauth2/permissionsaudit/grantedClients/:client_id/revoke
Request Path Parameter: client_id: the OAuth2 client that will no longer have access to the user’s resources and/or identity
Response: On successful revocation, return HTTP 200. No body.
Upon calling this method, the refresh token and access tokens held by the specified client for the authenticated user making the API call will be revoked.

Update metadata for a token

Endpoint: PUT /oauth2/audit/tokens/:token_id/metadata
Request Parameters:

  • client_id: the OAuth2 client that is associated with the token

  • token_id: the token to update

Request Body: OAuthRefreshTokenInformation

Response: On successful update, return HTTP 200. No body
Upon calling this method, the token identifier will be updated

In practice, only the token name can be updated.

User revocation of a particular token

Endpoint: POST /oauth2/audit/tokens/:token_id/revoke
Request Parameters:

  • client_id: the OAuth2 client that is associated with the token to revoke

  • token_id: the token to revoke

Response: On successful revocation, return HTTP 200. No body.

Upon calling this method, the refresh token and access tokens held by the specified client for the authenticated user making the API call will be revoked.

...

Endpoint: POST /oauth2/revoke
Request Body: OAuthTokenRevocationRequest
Response: By RFC 7009 § 2.2, on successful revocation, HTTP 200. No body.
Upon calling this method, the refresh/access token and associated tokens held by this client and associated with the user are revoked. Note: a specific path for this endpoint is not named by OAuth 2.0/OIDC specifications.

Requesting a new access token with a refresh token

Retrieval of token metadata

Endpoint (users): GET /oauth2/audit/tokens/:token_id/metadata
Endpoint (clients): GET /oauth2/token/:token_id/metadata
Request Parameter: token_id - the ID of the token to gather metadata about
Response: OAuthRefreshTokenInformation
The client can call this endpoint to get token metadata name. This metadata can be displayed to the user so that they may more easily identify the token in use when auditing/revoking tokens.

Additional grant types to issue tokens

Endpoint: POST /oauth2/token
This method exists. This feature proposal would add support for grant_type=refresh_token, and return a refresh token for grant_type=code. For details, see OIDC Core 1.0 § 12.1, 12.2.

Backend Implementation Detail: Database Model

This section covers implementation details that will not be visible to users of the new services, and is not necessary to read to have an understanding of how to use the new services.

To support revoking access tokens, we will need to track refresh tokens in the database

New DB Table: OAUTH_REFRESH_TOKENS
id: integer, primary key
token: CHAR(36) (semantically, a UUID)
created_on: TIMESTAMP
user_id: BIGINT (referencing the principal user)
client_id: BIGINT (referencing the client)
last_used: TIMESTAMP
Uniqueness constraint on (user_id, client_id)

...

Public vs. Confidential Clients

We will initially require that all of our clients are confidential. Public clients (where credentials are not secrets, e.g. a native application or single page app), are permitted by the OAuth specification, but require additional security consideration that we do not support at this time.

FAQ/Anticipated Concerns

Does adding refresh tokens break any existing behavior?

It should not because we are merely extending the access token with a reference to its refresh token ID. Current clients would not see the refresh token without requesting the offline_access scope, and if they did receive it, they may ignore it. The duration of access tokens is unchanged.

Open Questions

Is a UUID a good choice for the refresh token?.

PKCE - What does it protect against?

This design document has been revised to only support confidential OAuth clients at this time. The threat mitigated by PKCE only applies to certain types of public clients, so it is not necessary for this feature. We should consider adding it when we support public/native OAuth clients.

Expand
titlePKCE threat model and explanation

PKCE is an additional safeguard that specifically applies to native applications (e.g. a command line app) using the authorization code flow (i.e. the user authenticates in a browser, and is redirected back to the native app with an authorization code).

The threat in this situation is that when the user is redirected back to the app through a custom URI or to a temporary local webserver, a malicious app may be the recipient of the redirect, rather than the intended client. The malicious app now has access to the authorization code. Because the client credentials are not confidential (due to the intended application being a native app), the authorization code gives the malicious app access to the user’s account.

The solution is PKCE:

  1. The client generates a secret. The secret is securely stored where a malicious app cannot find it.

  2. The client sends the hashed (SHA256) secret to the server on the authorization request (POST /oauth2/consent)

  3. When exchanging the authorization code for access/refresh tokens (POST /oauth2/token?grant_type=code), the unhashed secret for the call to succeed.

If the authorization code is hijacked, the malicious app cannot use it because it does not know the additional secret.

Open Questions

Choice of a refresh token? Proposed: 256 bit random string using SecureRandom

Should (When) should a refresh token expire?

  • Per sections 1.5 and 10.4 of the OAuth 2.0 spec (by way of this StackOverflow post), it seems we have some liberty in terms of the lifecycle of a refresh token.

  • Some options we have include

    • Refresh tokens last until they are revoked

      Simple
    • Refresh tokens are leased (i.e. they expire X days after last use)

    • Rotate the refresh token after each use

      Trades additional client complexity for security

    • Refresh tokens expire after a duration

  • The proposed API design for user revocation of a token assumes “no”, because a user revokes access using the client ID.

  • Other applications seem to only allow one token per user (listing your granted application integrations in Google, GitHub, etc. won’t show the same client more than once, as far as I know)

  • As a user, revoking a token is confusing if you have more than one token per client (“Which instance of Client X permissions do I want to revoke?”).

  • What happens when a client attempts to create a new refresh token, when one exists (e.g. to ask for more scope)

    • Probably just invalidate the old refresh token, and create a new one
      • User will be required to reauthorize the application after a certain period of time (e.g. one year)

      • Functionally, this is not much different than a long-lived access token, so I don’t think this is the best option

  • This decision should be driven by use-cases

Should a client be able to possess more than one refresh token per user at a time?

Is there a compelling use case for a user to be able to see the access they have given in the past but have revoked? (i.e. do we keep a record of revoked refresh tokens?)

Should we limit the number of active refresh tokens per (user, client) pair?

  • Google limits this to 50 and expires the least recently used token when a new token is issued

  • Implications

    • Simplifying an interface where a user is trying to audit/revoke tokens

    • Limits number of jobs that can be submitted to a workflow queue