Skip to content
299 changes: 200 additions & 99 deletions content/client-server-api/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1481,14 +1481,107 @@ MAY reject weak passwords with an error code `M_WEAK_PASSWORD`.

### OAuth 2.0 API

#### Server metadata discovery

{{% http-api spec="client-server" api="oauth_server_metadata" %}}

#### Scope

The client requests a scope in the OAuth 2.0 authorization flow, which is then
associated to the generated access and refresh tokens. This provides a framework
for obtaining user consent.

A scope is defined in [RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3)
as a string containing a list of space-separated scope tokens.

{{% boxes/note %}}
The framework encourages the practice of obtaining additional user consent when
a client asks for a new scope that was not granted previously. This could be
used by future MSCs to replace the legacy [User-Interactive Authentication API](#user-interactive-authentication-api).
{{% /boxes/note %}}

##### Scope token format

All scope tokens related to Matrix should start with `urn:matrix:` and use the
`:` delimiter for further sub-division.

Scope tokens related to mapping of Client-Server API access levels should start
with `urn:matrix:client:`.

{{% boxes/note %}}
For MSCs that build on this namespace, unstable subdivisions should be used
whilst in development. For example, if MSCXXXX wants to introduce the
`urn:matrix:client:foo` scope, it could use
`urn:matrix:client:com.example.mscXXXX.foo` during development.
If it needs to introduce multiple scopes, like `urn:matrix:client:foo` and
`urn:matrix:client:bar`, it could use
`urn:matrix:client:com.example.mscXXXX:foo` and
`urn:matrix:client:com.example.mscXXXX:bar`.
{{% /boxes/note %}}

##### Allocated scope tokens

This specification defines the following scope tokens:
- [`urn:matrix:client:api:*`](#full-client-server-api-readwrite-access)
- [`urn:matrix:client:device:<device_id>`](#device-id-allocation)

###### Full client-server API read/write access

| Scope | Purpose |
|---------------------------|---------------------------------------------|
| `urn:matrix:client:api:*` | Grants full access to the Client-Server API. |

{{% boxes/note %}}
This token matches the behavior of the legacy authentication API. Future MSCs
could introduce more fine-grained scope tokens like
`urn:matrix:client:api:read:*` for read-only access.
{{% /boxes/note %}}

###### Device ID allocation

| Scope | Purpose |
|----------------------------------------|----------------------------------------------------------------------------------------------|
| `urn:matrix:client:device:<device_id>` | Allocates the given `device_id` and associates it to the generated access and refresh tokens. |

Contrary to the legacy login and registration APIs where the homeserver is
typically the one generating a `device_id` and providing it to the client, with
the OAuth 2.0 API, the client is responsible for allocating the `device_id`.

There MUST be exactly one `urn:matrix:client:device:<device_id>` token in the
requested scope in the login flow.

When generating a new `device_id`, the client SHOULD generate a random string
with enough entropy. It SHOULD only use characters from the unreserved character
list defined by [RFC 3986 section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3):

```
unreserved = a-z / A-Z / 0-9 / "-" / "." / "_" / "~"
```

Using this alphabet, a 10 character string is enough to stand a sufficient
chance of being unique per user. The homeserver MAY reject a request for a
`device_id` that is not long enough or contains characters outside the
unreserved list.

In any case it MUST only use characters allowed by the OAuth 2.0 scope
definition in [RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3),
which is defined as the following ASCII ranges:

```
%x21 / %x23-5B / %x5D-7E
```

This definition matches:
- alphanumeric characters: `A-Z`, `a-z`, `0-9`
- the following characters: ``! # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~``

#### Grant types

OAuth 2.0 defines several ways in [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)
and other RFCs to obtain an access token at the token endpoint, these are called
grants.
[RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) and other RFCs define
several "grant types": ways to obtain an ["access token"](#using-access-tokens).

All these grants require the client to know the following authorization server
metadata:
All these grants types require the client to know the following authorization
server metadata:
- `token_endpoint`
- `grant_types_supported`

Expand All @@ -1498,11 +1591,6 @@ This specification supports the following grant types:
- [Authorization code grant](#authorization-code-grant)
- [Refresh token grant](#refresh-token-grant)

{{% boxes/note %}}
Other MSCs might add support for more grant types in the future, like [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108)
which makes use of the device code grant.
{{% /boxes/note %}}

##### Authorization code grant

As per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1),
Expand All @@ -1516,53 +1604,102 @@ metadata:
- `response_mode_supported`

To use this grant, homeservers and clients MUST:
- support the authorization code grant as per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1)
- support the [refresh token grant](#refresh-token-grant)
- support PKCE using the `S256` code challenge method as per [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)
- use pre-registered, strict redirect URIs
- use the `fragment` response mode as per [OAuth 2.0 Multiple Response Type

- Support the authorization code grant as per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1).
- Support the [refresh token grant](#refresh-token-grant).
- Support PKCE using the `S256` code challenge method as per [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636).
- Use pre-registered, strict redirect URIs.
- Use the `fragment` response mode as per [OAuth 2.0 Multiple Response Type
Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)
for clients with an HTTPS redirect URI
for clients with an HTTPS redirect URI.

###### User registration

Clients can signal to the server that the user desires to register a new account
by initiating the authorization code grant with the `prompt=create` parameter
set in the authorization request as defined in [Initiating User Registration via
OpenID Connect 1.0](https://openid.net/specs/openid-connect-prompt-create-1_0.html).

Whether the homeserver supports this parameter is advertised by the
`prompt_values_supported` authorization server metadata.

Servers that support this parameter SHOULD show the account registration UI in
the browser.

##### Refresh token grant

As per [RFC 6749 section 6](https://datatracker.ietf.org/doc/html/rfc6749#section-6),
the refresh token grant lets the client exchange a refresh token for an access
token.

When authorization is granted to a client, the homeserver MUST issue a refresh
token to the client in addition to the access token.

The access token MUST be short-lived and SHOULD be refreshed using the
`refresh_token` when expired.

The homeserver SHOULD issue a new refresh token each time an old one is used,
and invalidate the old one. However, it MUST ensure that the client is able to
retry the refresh request in the case that the response to the request is lost.

The homeserver SHOULD consider that the session is compromised if an old,
invalidated refresh token is used, and SHOULD revoke the session.

The client MUST handle access token refresh failures as follows:

###### Login flow with the authorization code grant
- If the refresh fails due to network issues or a `5xx` HTTP status code from
the server, the client should retry the request with the old refresh token
later.
- If the refresh fails due to a `4xx` HTTP status code from the server, the
client should consider the session logged out.

#### Login flow

Logging in with the OAuth 2.0 authorization code grant in the context of the
Matrix specification means to request a scope including full client-server API
Logging in with the OAuth 2.0 API should be done with the [authorization code
grant](#authorization-code-grant). In the context of the Matrix specification,
this means requesting a [scope](#scope) including full client-server API
read/write access and allocating a device ID.

First, the client needs to generate the following values:

- a random value for the `device_id`
- a random value for the `state`
- a cryptographically random value for the `code_verifier`
- `device_id`: a unique identifier for this device; see the
[`urn:matrix:client:device:<device_id>`] scope.
- `state`: a unique opaque identifier, like a [transaction ID](#transaction-identifiers),
that will allow the client to maintain state between the authorization request
and the callback.
- `code_verifier`: a cryptographically random value that will allow to make sure
that the client that makes the token request for a given `code` is the same
one that made the authorization request.

It is defined in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) as
a high-entropy cryptographic random string using the characters `[A-Z]`,
`[a-z]`, `[0-9]`, `-`, `.`, `_` and `~` with a minimum length of 43 characters
and a maximum length of 128 characters.

**Authorization request**

It then constructs the authorization request URL using the
The client then constructs the authorization request URL using the
`authorization_endpoint` value, with the following query parameters:

- The `response_type` value set to `code`
- The `client_id` value obtained by registering the client metadata with the
server
- The `redirect_uri` value that MUST match one of the values registered in the
client metadata
- The `scope` value set to `urn:matrix:client:api:* urn:matrix:client:device:<device_id>` with the `device_id` generated previously
- The `state` value
- The `response_mode` value
- The `code_challenge` computed from the `code_verifier` value using the SHA-256
algorithm, as described in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)
- The `code_challenge_method` set to `S256`
| Parameter | Value |
|-------------------------|----------------------------------------------------|
| `response_type` | `code` |
| `client_id` | The client ID returned from client registration. |
| `redirect_uri` | The redirect URI that MUST match one of the values registered in the client metadata |
| `scope` | `urn:matrix:client:api:* urn:matrix:client:device:<device_id>` with the `device_id` generated previously. |
| `state` | The `state` value generated previously. |
| `response_mode` | `fragment` or `query` (see "[Callback](#callback)" below). |
| `code_challenge` | Computed from the `code_verifier` value generated previously using the SHA-256 algorithm, as described in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) |
| `code_challenge_method` | `S256` |

This authorization request URL must be opened in the user's browser:

- For web-based clients, this can be done through a redirection or by opening
the URL in a new tab
- For native clients, this can be done by opening the URL:
- using the system browser
- through platform-specific APIs when available, such as
[`ASWebAuthenticationSession`](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)
on iOS or [Android Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs)
on Android
the URL in a new tab.
- For native clients, this can be done by opening the URL using the system
browser, or, when available, through platform-specific APIs such as
[`ASWebAuthenticationSession`](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)
on iOS or [Android Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs).

Sample authorization request, with extra whitespaces for readability:

Expand All @@ -1578,23 +1715,23 @@ https://account.example.com/oauth2/auth?
code_challenge_method = S256
```

**Callback**
<a id="callback"></a> **Callback**

Once completed, the user is redirected to the `redirect_uri`, with either a
successful or failed authorization in the URL fragment or query parameters.
Whether the parameters are in the URL fragment or query parameters is determined
by the `response_mode` value:

- if set to `fragment`, the parameters will be placed in the URL fragment, like
`https://example.com/callback#param1=value1&param2=value2`
- if set to `query`, the parameters will be in placed the query string, like
`com.example.app:/callback?param1=value1&param2=value2`
- If set to `fragment`, the parameters will be placed in the URL fragment, like
`https://example.com/callback#param1=value1&param2=value2`.
- If set to `query`, the parameters will be in placed the query string, like
`com.example.app:/callback?param1=value1&param2=value2`.

To avoid disclosing the parameters to the web server hosting the `redirect_uri`,
clients should use the `fragment` response mode if the `redirect_uri` is an
clients SHOULD use the `fragment` response mode if the `redirect_uri` is an
HTTPS URI with a remote host.

In both success and failure cases, the parameters will have the `state` value
In both success and failure cases, the parameters will include the `state` value
used in the authorization request.

A successful authorization will have a `code` value, for example:
Expand Down Expand Up @@ -1623,11 +1760,13 @@ the token endpoint.
This is done by making a POST request to the `token_endpoint` with the following
parameters, encoded as `application/x-www-form-urlencoded` in the body:

- The `grant_type` set to `authorization_code`
- The `code` obtained from the callback
- The `redirect_uri` used in the authorization request
- The `client_id` value
- The `code_verifier` value generated at the start of the authorization flow
| Parameter | Value |
|-----------------|------------------------------------------------------------|
| `grant_type` | `authorization_code` |
| `code` | The value of `code` obtained from the callback. |
| `redirect_uri` | The same `redirect_uri` used in the authorization request. |
| `client_id` | The client ID returned from client registration. |
| `code_verifier` | The value generated at the start of the authorization flow. |

The server replies with a JSON object containing the access token, the token
type, the expiration time, and the refresh token.
Expand Down Expand Up @@ -1662,58 +1801,20 @@ Sample response:
Finally, the client can call the [`/whoami`](#get_matrixclientv3accountwhoami)
endpoint to get the user ID that owns the access token.

###### User registration

Clients can signal to the server that the user desires to register a new account
by initiating the authorization code grant with the `prompt=create` parameter
set in the authorization request as defined in [Initiating User Registration via
OpenID Connect 1.0](https://openid.net/specs/openid-connect-prompt-create-1_0.html).

Whether the homeserver supports this parameter is advertised by the
`prompt_values_supported` authorization server metadata.

Servers that support this parameter SHOULD show the account registration UI in
the browser.

##### Refresh token grant

As per [RFC 6749 section 6](https://datatracker.ietf.org/doc/html/rfc6749#section-6),
the refresh token grant lets the client exchange a refresh token for an access
token.

When authorization is granted to a client, the homeserver MUST issue a refresh
token to the client in addition to the access token.

The access token MUST be short-lived and SHOULD be refreshed using the
`refresh_token` when expired.

The homeserver SHOULD issue a new refresh token each time one is used, and
invalidate the old one. It should do this only if it can guarantee that in case
a response with a new refresh token is not received and stored by the client,
retrying the request with the old refresh token will succeed.

The homeserver SHOULD consider that the session is compromised if an old,
invalidated refresh token is being used, and SHOULD revoke the session.

The client MUST handle access token refresh failures as follows:

- If the refresh fails due to network issues or a `5xx` HTTP status code from
the server, the client should retry the request with the old refresh token
later.
- If the refresh fails due to a `4xx` HTTP status code from the server, the
client should consider the session logged out.
#### Token refresh flow

###### Token refresh flow with the refresh token grant
Refreshing a token with the OAuth 2.0 API should be done with the [refresh token
grant](#refresh-token-grant).

When the access token expires, the client must refresh it by making a `POST`
request to the `token_endpoint` with the following parameters, encoded as
`application/x-www-form-urlencoded` in the body:

- The `grant_type` set to `refresh_token`
- The `refresh_token` obtained from the token response during the authorization
flow
- The `client_id` value obtained by registering the client metadata with the
server
| Parameter | Value |
|-----------------|------------------------------------------------------------|
| `grant_type` | `refresh_token` |
| `refresh_token` | The `refresh_token` obtained from the token response during the last token request. |
| `client_id` | The client ID returned from client registration. |

The server replies with a JSON object containing the new access token, the token
type, the expiration time, and a new refresh token, like in the authorization
Expand Down