Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC2964: Usage of OAuth 2.0 authorization code grant and refresh token grant #2964

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2af8b49
OAuth 2.0 profile MSC
sandhose Jan 14, 2021
5962618
Refer to OP rather than AS to avoid clash with Application Service
hughns May 25, 2022
38c97a5
Title update and intro about architectural change
hughns May 25, 2022
e12ee77
Add section on endpoints that would now be outside of scope and so re…
hughns Feb 8, 2023
20d865d
Spelling
hughns Feb 8, 2023
261e3b0
Section on proposed endpoints that would no longer be relevant
hughns Feb 8, 2023
38bb557
Consistency with MSC3861 and cleanup
hughns Feb 8, 2023
45d510b
Standardise terminology on OpenID Provider = OP
hughns Feb 8, 2023
d114f82
Update proposals/2964-oauth2-profile.md
hughns Feb 8, 2023
8fc3ea1
Notes on QR and browserless
hughns Feb 8, 2023
029e1e5
OpenID id_token endpoint is still needed
hughns Feb 8, 2023
0802d8f
Notes about confusion with existing OIDC and OpenID capabilities
hughns Feb 9, 2023
20ee4a3
Additional endpoints to be removed
hughns Feb 13, 2023
6e387d8
Add 3pid endpoints that would be removed
hughns Feb 15, 2023
4a2ed74
Changes to GET /account/3pid
hughns Feb 15, 2023
40048da
Alternative proposal for 3PID handling
hughns Feb 15, 2023
f0e319a
Add section on removing UIA
hughns Feb 16, 2023
55215c1
Refer to UIA as API
hughns Feb 17, 2023
21fee1c
We now have proposal for 3PID and guest access
hughns Feb 20, 2023
2c0625d
Logout semantics
hughns Feb 23, 2023
24e0290
Remove TBDs that are done
hughns Feb 23, 2023
d145fd2
Merge branch 'msc/sandhose/oauth2-profile' of https://github.com/sand…
hughns Feb 23, 2023
acfa845
More done items
hughns Feb 23, 2023
fa506ff
Remove dependency loop
hughns Apr 6, 2023
378348e
Merge branch 'matrix-org:main' into msc/sandhose/oauth2-profile
sandhose Sep 3, 2024
c859c0b
Rework proposal to only cover the authorization code flow
sandhose Sep 4, 2024
c1c8312
Fix a bunch of todos
sandhose Sep 16, 2024
05748a2
Fix typos
sandhose Sep 16, 2024
1034122
Fix the response_mode being an authorization request parameter
sandhose Sep 17, 2024
4830d47
Apply suggestions from code review
sandhose Sep 17, 2024
f84428f
Remove unused images
sandhose Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions proposals/2964-oauth2-profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# MSC2964: Usage of OAuth 2.0 authorization code grant and refresh token grant

This proposal is part of the broader [MSC3861: Next-generation auth for Matrix, based on OAuth 2.0/OIDC][MSC3861].

This MSC in particular defines how clients can leverage the OAuth 2.0 authorization code grant to gain access to the Matrix Client-to-Server API.

## Proposal

### Prerequisites

This proposal requires the client to know the following authorization server metadata about the homeserver:

- `authorization_endpoint`: the URL where the user should be sent to initiate the login flow
- `token_endpoint`: the URL where the client is able to exchange the authorization code for an access token
- `response_types_supported`: a JSON array of response types supported by the authorization endpoint
- `grant_types_supported`: a JSON array of grant types supported by the authorization endpoint defined in [RFC8414] and used in [RFC6749]
- `response_mode_supported`: a JSON array of response modes supported by the authorization endpoint

All of those metadata values are well-defined in [RFC8414] and used in various RFCs like [RFC6749].

The discovery of the above metadata is out of scope for this MSC, and is currently covered by [MSC2965](https://github.com/matrix-org/matrix-doc/pull/2965).

The client must also have a `client_id` to use with this flow.
How the client obtains this is out of scope for this MSC, and is currently covered by [MSC2966](https://github.com/matrix-org/matrix-doc/pull/2966).

### Authorization code grant

As per [RFC6749], the authorization code grant lets the client obtain an access token through a browser redirect.

Because this flow has various parameters and security improvements added by other specifications, this describes what is enforced and required to support by the client and the homeserver.

Homeservers and clients must:

- support PKCE as per [RFC7636]
- support the auth code flow as per [RFC6749] section 4.1
- support the refresh token grant as per [RFC6749] section 6
- use pre-registered, strict redirect URIs
- use the `fragment` response mode as per [OAuth 2.0 Multiple Response Type Encoding Practices] for public clients with an HTTPS redirect URI

### Refresh token grant

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, as described in [RFC6749] section 6.

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.

### Sample flow

#### Flow parameters

The client must know the following parameters, through ways described in [MSC2965], [MSC2966] and [MSC2967]:

- `authorization_endpoint`: the URL where the user is able to access the authorization endpoint to initiate the login flow
- `token_endpoint`: the URL where the user is able to access the token endpoint to exchange the authorization code for an access token
- `client_id`: the unique identifier allocated for the client
- `redirect_uri`: the URI where the user is redirected after the authorization flow used by this client
- `scope`: the scope of the access token to request
- `response_mode`: the response mode to use, either `fragment` or `query`. It must be `fragment` if the `redirect_uri` is an HTTPS URI, and can be `query` otherwise

It needs to generate the following values:

- Generate a random value for the `state`
- Generate a 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
- The `redirect_uri` value
- The `scope` value
- The `state` value
- The `response_mode` value
- The `code_challenge` computed from the `code_verifier` value using the SHA-256 algorithm, as described in [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 rediretion 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 rationale for using the system browser is explained in [MSC3861], under "Motivation" → "Benefits of authenticating end-users through the system browser".

---

Sample authorization request (broken down into multiple lines for readability), with the following values:

- `authorization_endpoint` set to `https://account.example.com/oauth2/auth`, obtained through [MSC2965]
- `client_id` set to `s6BhdRkqt3`, obtained through [MSC2966]
- `redirect_uri` set to `https://app.example.com/oauth2-callback`
- `state` set to `ewubooN9weezeewah9fol4oothohroh3`
- `response_mode` set to `fragment`
- `code_verifier` set to `ogie4iVaeteeKeeLaid0aizuimairaCh`
- `code_challenge` computed as `72xySjpngTcCxgbPfFmkPHjMvVDl2jW1aWP7-J6rmwU`
- `scope` set to `urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD` (as per [MSC2967])

```
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 &
Comment on lines +109 to +110

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
redirect_uri = https://app.example.com/oauth2-callback &
scope = urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD &
redirect_uri = https%3A%2F%2Fapp.example.com%2Foauth2-callback &
scope = urn%3Amatrix%3Aclient%3Aapi%3A*+urn%3Amatrix%3Aclient%3Adevice%3AAAABBBCCCDDD &

These values would be URL encoded

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this part simplified with no URL-encoding and extra spaces. It might be worth saying that, and give the actual url-encoded, with no space added URL in addition

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values are already listed above in their non-url-encoded format. The oauth specs also include line breaks in their examples but url-encode the values

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 parameter 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 `https://example.com/callback?param1=value1&param2=value2`

Public clients with an HTTPS redirect URI must use the `fragment` response mode, as the fragment is not sent to the server in the redirect.

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

Successful authorization will have a `code` value.

Sample successful authorization:

```
https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R
```

---

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)

Sample failed authorization:

```
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&redirect_uri=https://app.example.com/oauth2-callback
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth2-callback

URL encoding

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this is done for readability

&client_id=s6BhdRkqt3
&code_verifier=ogie4iVaeteeKeeLaid0aizuimairaCh
```

```json
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 299,
"refresh_token": "tGz3JOkF0XG5Qx2TlKWIA",
"scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD"
}
```

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

#### Token refresh

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
- The `client_id` value

The server replies with a JSON object containing the new access token, the token type, the expiration time, and a new refresh token.
The old refresh token is no longer valid and should be discarded.

Sample token refresh:

```
POST /oauth2/token HTTP/1.1
Host: account.example.com
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=refresh_token
&refresh_token=tGz3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3
```

```json
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 299,
"refresh_token": "tGz3JOkF0XG5Qx2TlKWIA",
"scope": "urn:matrix:client:api:* urn:matrix:client:device:AAABBBCCCDDD"
}
```

### User registration

User can register themselves by initiating a authorization code flow with the `prompt=create` parameter 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.

## Potential issues

For a discussion on potential issues please see [MSC3861]

## Alternatives

For a discussion on alternatives please see [MSC3861]

## Security considerations

Since this touches one of the most sensitive part of the API, there are a lot of security considerations to have.

The [OAuth 2.0 Security Best Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-16) IETF draft has many attack scenarios. Many of those scenarios are mitigated by the choices enforced in the client profiles outlined in this MSC.

## Unstable prefix

None as part of this MSC.

## Dependencies

- [MSC2965]
- [MSC2966]
- [MSC2967]

[RFC6749]: https://tools.ietf.org/html/rfc6749
[RFC7636]: https://tools.ietf.org/html/rfc7636
[RFC8414]: https://tools.ietf.org/html/rfc8414
[MSC2965]: https://github.com/matrix-org/matrix-spec-proposals/pull/2965
[MSC2966]: https://github.com/matrix-org/matrix-spec-proposals/pull/2966
[MSC2967]: https://github.com/matrix-org/matrix-spec-proposals/pull/2967
[MSC3861]: https://github.com/matrix-org/matrix-spec-proposals/pull/3861
[OAuth 2.0 Multiple Response Type Encoding Practices]: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html