Requirements for OAuth 2 public clients
The initial purpose of the document was to identify OAuth 2 as a solution for replacing API keys in the command line clients. We determined that OAuth 2 is not a good fit for the command line clients, so design work for that issue has been moved to Personal Access Tokens. OAuth 2 public client implementation has not begun, as of the time of writing.
This document still outlines requirements for OAuth 2 public clients, which may be worth implementing in the future.
Summary
Using OAuth 2 to authenticate with Synapse has many benefits over using older authentication mechanisms. We can try to consolidate authentication services to using OAuth flows wherever possible. This document focuses on enabling the creation of OAuth 2 public clients, which would allow OAuth 2 based authentication in Synapse command line apps and third-party browser-based SPAs. Additionally, this will hasten the deprecation of API keys.
To securely support OAuth 2 public clients, these actions are needed:
Support PKCE in the authorization code flow:
PLFM-6337 - Getting issue details... STATUSAdd field to label OAuth 2 clients as either “public” or “confidential”; backfill existing clients as “confidential”:
PLFM-6338 - Getting issue details... STATUSPublic clients should not be issued secrets
Note: Refresh tokens become single-use bearer tokens.
Public clients should be required to use PKCE
Don’t save consent records for public OAuth clients
Revoke the current iteration of a refresh token when an expired/used refresh token is used:
PLFM-6339 - Getting issue details... STATUS
To fully support Synapse command line clients, in addition to the above, we must:
Add a bootstrapped OAuth client for Synapse command line apps:
PLFM-6340 - Getting issue details... STATUSAdd service to issue a (user-authenticated) refresh token for the Synapse command line apps OAuth client:
PLFM-6341 - Getting issue details... STATUS
Background
There are currently many ways to authenticate a request to Synapse, including
Session tokens. Session tokens grant access to all account functions. Session tokens expire after 24 hours, and they can also be refreshed (for an additional 24 hours) and revoked. To acquire a session token, users must either
Enter their username and password
Sign in using Google as an OIDC identity provider.
API keys. API keys grant access to all account functions. API keys do not expire, but they can be revoked, and a new key can be generated. Users may only have one API key active at a time. A user may retrieve their API key at any time (and as an implication, they are stored unhashed in Synapse).
Using an access token. Access tokens last 24 hours. Access tokens are also scoped, so that a token may only be used to perform specific types of actions. Only verified OAuth 2 clients may be issued access tokens. At this time, access tokens cannot be revoked unless they are issued with a refresh token.
There are two ways that a client can obtain an access token, both requiring client credentials.
Sending Synapse a valid authorization code, which is granted when a user authorizes the OAuth client. Authorization codes expire after one minute.
Sending Synapse a valid refresh token. The refresh token is granted when the user authorizes an app with the
offline_accessscope. Refresh tokens expire 180 days after being issued and are single-use, though a new refresh token is issued when one is used, so access should be considered non-expiring. Refresh tokens can be revoked, and access tokens associated with/granted by the refresh token will also be revoked. Additionally, we only store the hash of the refresh token, so the token can only be retrieved at the time it is generated.
Table Summary
| Session Token | API Key | OAuth 2.0 access/refresh token |
|---|---|---|---|
Access Granted | Entire account | Entire account | Scoped, defined by the client |
Expires | 24 hrs after being issued | Forever | Access: 24 hrs after issued Refresh: 180 days after issued |
Revocable | Yes | Yes | Only if associated with refresh token |
Maximum that can be issued | 1 | 1 | Access: Unlimited Refresh: 100 per user per OAuth client |
Stored as/Re-retrievable | Unhashed/Yes | Unhashed/Yes | Access: Not stored/No Refresh: Hashed token/No |
Motivation
We can reduce the complexity and attack surface of authentication with Synapse by utilizing OAuth 2 login flows in places where other mechanisms are currently used.
In particular, we can aim to replace usage of the API key with OAuth refresh tokens. The most common use case for the API key is to authenticate Synapse command line clients. Using refresh/access tokens in place of an API key provides a couple of advantages:
Leased/timed expiration - if a particular session isn’t used for 180 days, the token expires
The ability to grant/revoke access on the machine-level, instead of having one key used everywhere
Scoped access, in cases where the command line app is running a job that only needs a subset of access/functionality
Using either the authorization code (currently supported) or device code (not currently supported) OAuth 2 flows require users to login on Synapse.org via a browser, instead of entering credentials at the command line, or pasting an API key into a config file.
The caveat to using OAuth 2.0 for native apps and JavaScript-based SPAs is that in these circumstances, the OAuth client cannot securely and confidentially store credentials. This scenario is a use case that is outlined in the OAuth 2.0 specification, but we currently expect all Synapse OAuth clients to be confidential clients. Therefore, we must consider the security and access implications of creating/allowing public OAuth clients.
Requirements/Notes/Considerations
This section contains an explanation of each requirement for supporting OAuth 2 public clients in Synapse. This section is summarized at the top of this document, and services are enumerated at the bottom of this document.
PKCE
Best practices suggest that authorization servers are required to support PKCE [2][3]. PKCE only provides benefits to public OAuth clients, since a malicious app must have knowledge of an OAuth client’s credentials to execute the attack.
For more details, expand the following section:
Designating new clients as either public or confidential
All existing clients are considered to be “confidential” clients, so we can easily backfill that information.
We can then use the public/confidential label to handle logic accordingly [2]:
Authorization servers MUST record the client type in the client registration details in order to identify and process requests accordingly.
As an example, we can require that public clients utilize PKCE, among other considerations.
In OAuth 2.0, the authorization server SHOULD NOT assume client type [1]. We can require client creators to specify their client type upon creation. This is a breaking API change, but it is acceptable because OAuth clients aren’t created programmatically (it wouldn’t make sense if they are, since they must be manually verified to be functional).
Public clients may or may not be issued a secret
Client credentials (other than the ID) are not useful for public clients [1]:
The authorization server MAY establish a client authentication method with public clients. However, the authorization server MUST NOT rely on public client authentication for the purpose of identifying the client.