From 101d0fe34f6b7f7bd033c8c49e35e9e3cfc56550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 25 May 2025 13:17:41 +0200 Subject: [PATCH 1/7] Add OAuth 2.0 authorization code and refresh token grant types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per MSC2964 Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 238 ++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 855e8df9e..0b253f5a4 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1481,6 +1481,244 @@ MAY reject weak passwords with an error code `M_WEAK_PASSWORD`. ### OAuth 2.0 API +#### 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. + +All these grants require the client to know the following authorization server +metadata: +- `token_endpoint` +- `grant_types_supported` + +The client must also have obtained a `client_id` by registering with the server. + +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), +the authorization code grant lets the client obtain an access token through a +browser redirect. + +This grant requires the client to know the following authorization server +metadata: +- `authorization_endpoint` +- `response_types_supported` +- `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 + Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) + for clients with an HTTPS redirect URI + +###### Login flow with the authorization code grant + +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 +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` + +**Authorization request** + +It 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:` 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` + +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 + +Sample authorization request, with extra whitespaces for readability: + +``` +https://account.example.com/oauth2/auth? + client_id = s6BhdRkqt3 & + response_type = code & + response_mode = fragment & + redirect_uri = https://app.example.com/oauth2-callback & + scope = urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD & + state = ewubooN9weezeewah9fol4oothohroh3 & + code_challenge = 72xySjpngTcCxgbPfFmkPHjMvVDl2jW1aWP7-J6rmwU & + code_challenge_method = S256 +``` + +**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¶m2=value2` +- if set to `query`, the parameters will be in placed the query string, like + `com.example.app:/callback?param1=value1¶m2=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 +HTTPS URI with a remote host. + +In both success and failure cases, the parameters will have the `state` value +used in the authorization request. + +A successful authorization will have a `code` value, for example: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R +``` + +A failed authorization will have the following values: + +- `error`: the error code +- `error_description`: the error description (optional) +- `error_uri`: the URI where the user can find more information about the error (optional) + +For example: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&error_uri=https%3A%2F%2Ferrors.example.com%2F +``` + +**Token request** + +The client then exchanges the authorization code to obtain an access token using +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 + +The server replies with a JSON object containing the access token, the token +type, the expiration time, and the refresh token. + +Sample token request: + +``` +POST /oauth2/token HTTP/1.1 +Host: account.example.com +Content-Type: application/x-www-form-urlencoded +Accept: application/json + +grant_type=authorization_code + &code=iuB7Eiz9heengah1joh2ioy9ahChuP6R + &redirect_uri=https://app.example.com/oauth2-callback + &client_id=s6BhdRkqt3 + &code_verifier=ogie4iVaeteeKeeLaid0aizuimairaCh +``` + +Sample response: + +```json +{ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "Bearer", + "expires_in": 299, + "refresh_token": "tGz3JOkF0XG5Qx2TlKWIA", + "scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD" +} +``` + +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 with the 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 + +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 +flow. + ### Account moderation #### Account locking From 0c50195a322756b5e6862632819d49a95a1bfddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 25 May 2025 13:20:37 +0200 Subject: [PATCH 2/7] Add changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- changelogs/client_server/newsfragments/2150.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/client_server/newsfragments/2150.feature diff --git a/changelogs/client_server/newsfragments/2150.feature b/changelogs/client_server/newsfragments/2150.feature new file mode 100644 index 000000000..6eff5607c --- /dev/null +++ b/changelogs/client_server/newsfragments/2150.feature @@ -0,0 +1 @@ +Add the OAuth 2.0 based authentication API, as per [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) and its sub-proposals. From e8c2f9a2a25efbb7de7fe10d3148d2721caab9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 16 Jun 2025 12:15:34 +0200 Subject: [PATCH 3/7] Apply text suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 113 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 0b253f5a4..24db00f38 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1483,12 +1483,11 @@ MAY reject weak passwords with an error code `M_WEAK_PASSWORD`. #### 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` @@ -1498,11 +1497,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), @@ -1516,13 +1510,14 @@ 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. ###### Login flow with the authorization code grant @@ -1532,37 +1527,44 @@ 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:`] 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:` 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:` 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: @@ -1578,23 +1580,23 @@ https://account.example.com/oauth2/auth? code_challenge_method = S256 ``` -**Callback** + **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¶m2=value2` -- if set to `query`, the parameters will be in placed the query string, like - `com.example.app:/callback?param1=value1¶m2=value2` +- If set to `fragment`, the parameters will be placed in the URL fragment, like + `https://example.com/callback#param1=value1¶m2=value2`. +- If set to `query`, the parameters will be in placed the query string, like + `com.example.app:/callback?param1=value1¶m2=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: @@ -1623,11 +1625,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. @@ -1687,13 +1691,12 @@ 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 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 being used, and SHOULD revoke the session. +invalidated refresh token is used, and SHOULD revoke the session. The client MUST handle access token refresh failures as follows: From 1c32e21b6924d9424a8eb2d9cf2e2b7139e7bf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 16 Jun 2025 12:27:29 +0200 Subject: [PATCH 4/7] Move up login and token refresh flows in hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 100 +++++++++++++++------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 24db00f38..c62e6b55b 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1519,10 +1519,51 @@ To use this grant, homeservers and clients MUST: Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) for clients with an HTTPS redirect URI. -###### Login flow with the authorization code grant +###### 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. -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 +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: + + - 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 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: @@ -1666,57 +1707,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). +#### Token refresh flow -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: - - - 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 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 From 572c2f31193bcf79b2bc78af467b96453e52208a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 18 Jun 2025 11:10:42 +0200 Subject: [PATCH 5/7] Apply review suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index e539788fc..42c5eb17b 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1580,12 +1580,12 @@ This definition matches: [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 types require the client to know the following authorization -server metadata: +All these grants types require the client to know the following [authorization +server metadata](#server-metadata-discovery): - `token_endpoint` - `grant_types_supported` -The client must also have obtained a `client_id` by registering with the server. +The client must also have obtained a `client_id` by [registering with the server](#client-registration). This specification supports the following grant types: - [Authorization code grant](#authorization-code-grant) @@ -1597,8 +1597,8 @@ As per [RFC 6749 section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#sect the authorization code grant lets the client obtain an access token through a browser redirect. -This grant requires the client to know the following authorization server -metadata: +This grant requires the client to know the following [authorization server +metadata](#server-metadata-discovery): - `authorization_endpoint` - `response_types_supported` - `response_mode_supported` @@ -1660,10 +1660,11 @@ 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: +Once the client has retrieved the [server metadata](#server-metadata-discovery), +it needs to generate the following values: - `device_id`: a unique identifier for this device; see the - [`urn:matrix:client:device:`] scope. + [`urn:matrix:client:device:`](#device-id-allocation) scope token. - `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. @@ -1689,7 +1690,7 @@ The client then constructs the authorization request URL using the | `scope` | `urn:matrix:client:api:* urn:matrix:client:device:` 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` | 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: From 9497d3c03db13e72d4fcb6d5db93fa44e5b0288e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 18 Jun 2025 11:12:15 +0200 Subject: [PATCH 6/7] Move "Login flow" and "Refresh Token flow" as the first sections of OAuth 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 336 ++++++++++++++-------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 42c5eb17b..714868f33 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1481,6 +1481,174 @@ MAY reject weak passwords with an error code `M_WEAK_PASSWORD`. ### OAuth 2.0 API +#### Login flow + +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. + +Once the client has retrieved the [server metadata](#server-metadata-discovery), +it needs to generate the following values: + +- `device_id`: a unique identifier for this device; see the + [`urn:matrix:client:device:`](#device-id-allocation) scope token. +- `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** + +The client then constructs the authorization request URL using the +`authorization_endpoint` value, with the following query parameters: + +| 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:` 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, 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: + +``` +https://account.example.com/oauth2/auth? + client_id = s6BhdRkqt3 & + response_type = code & + response_mode = fragment & + redirect_uri = https://app.example.com/oauth2-callback & + scope = urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD & + state = ewubooN9weezeewah9fol4oothohroh3 & + code_challenge = 72xySjpngTcCxgbPfFmkPHjMvVDl2jW1aWP7-J6rmwU & + code_challenge_method = S256 +``` + + **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¶m2=value2`. +- If set to `query`, the parameters will be in placed the query string, like + `com.example.app:/callback?param1=value1¶m2=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 +HTTPS URI with a remote host. + +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: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R +``` + +A failed authorization will have the following values: + +- `error`: the error code +- `error_description`: the error description (optional) +- `error_uri`: the URI where the user can find more information about the error (optional) + +For example: + +``` +https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&error_uri=https%3A%2F%2Ferrors.example.com%2F +``` + +**Token request** + +The client then exchanges the authorization code to obtain an access token using +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: + +| 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. + +Sample token request: + +``` +POST /oauth2/token HTTP/1.1 +Host: account.example.com +Content-Type: application/x-www-form-urlencoded +Accept: application/json + +grant_type=authorization_code + &code=iuB7Eiz9heengah1joh2ioy9ahChuP6R + &redirect_uri=https://app.example.com/oauth2-callback + &client_id=s6BhdRkqt3 + &code_verifier=ogie4iVaeteeKeeLaid0aizuimairaCh +``` + +Sample response: + +```json +{ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "Bearer", + "expires_in": 299, + "refresh_token": "tGz3JOkF0XG5Qx2TlKWIA", + "scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD" +} +``` + +Finally, the client can call the [`/whoami`](#get_matrixclientv3accountwhoami) +endpoint to get the user ID that owns the access token. + +#### Token refresh flow + +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: + +| 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 +flow. + #### Server metadata discovery {{% http-api spec="client-server" api="oauth_server_metadata" %}} @@ -1653,174 +1821,6 @@ The client MUST handle access token refresh failures as follows: - 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 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. - -Once the client has retrieved the [server metadata](#server-metadata-discovery), -it needs to generate the following values: - -- `device_id`: a unique identifier for this device; see the - [`urn:matrix:client:device:`](#device-id-allocation) scope token. -- `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** - -The client then constructs the authorization request URL using the -`authorization_endpoint` value, with the following query parameters: - -| 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:` 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, 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: - -``` -https://account.example.com/oauth2/auth? - client_id = s6BhdRkqt3 & - response_type = code & - response_mode = fragment & - redirect_uri = https://app.example.com/oauth2-callback & - scope = urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD & - state = ewubooN9weezeewah9fol4oothohroh3 & - code_challenge = 72xySjpngTcCxgbPfFmkPHjMvVDl2jW1aWP7-J6rmwU & - code_challenge_method = S256 -``` - - **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¶m2=value2`. -- If set to `query`, the parameters will be in placed the query string, like - `com.example.app:/callback?param1=value1¶m2=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 -HTTPS URI with a remote host. - -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: - -``` -https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R -``` - -A failed authorization will have the following values: - -- `error`: the error code -- `error_description`: the error description (optional) -- `error_uri`: the URI where the user can find more information about the error (optional) - -For example: - -``` -https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&error_uri=https%3A%2F%2Ferrors.example.com%2F -``` - -**Token request** - -The client then exchanges the authorization code to obtain an access token using -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: - -| 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. - -Sample token request: - -``` -POST /oauth2/token HTTP/1.1 -Host: account.example.com -Content-Type: application/x-www-form-urlencoded -Accept: application/json - -grant_type=authorization_code - &code=iuB7Eiz9heengah1joh2ioy9ahChuP6R - &redirect_uri=https://app.example.com/oauth2-callback - &client_id=s6BhdRkqt3 - &code_verifier=ogie4iVaeteeKeeLaid0aizuimairaCh -``` - -Sample response: - -```json -{ - "access_token": "2YotnFZFEjr1zCsicMWpAA", - "token_type": "Bearer", - "expires_in": 299, - "refresh_token": "tGz3JOkF0XG5Qx2TlKWIA", - "scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD" -} -``` - -Finally, the client can call the [`/whoami`](#get_matrixclientv3accountwhoami) -endpoint to get the user ID that owns the access token. - -#### Token refresh flow - -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: - -| 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 -flow. - ### Account moderation #### Account locking From 3a1479a48b2dd78cb9e0605167c627ef771680fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 18 Jun 2025 11:14:41 +0200 Subject: [PATCH 7/7] Add link to client registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- content/client-server-api/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 714868f33..a02af2fdd 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1776,7 +1776,7 @@ 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 [pre-registered](#client-registration), 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.