-
Notifications
You must be signed in to change notification settings - Fork 382
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
sandhose
wants to merge
31
commits into
matrix-org:main
Choose a base branch
from
sandhose:msc/sandhose/oauth2-profile
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
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 5962618
Refer to OP rather than AS to avoid clash with Application Service
hughns 38c97a5
Title update and intro about architectural change
hughns e12ee77
Add section on endpoints that would now be outside of scope and so re…
hughns 20d865d
Spelling
hughns 261e3b0
Section on proposed endpoints that would no longer be relevant
hughns 38bb557
Consistency with MSC3861 and cleanup
hughns 45d510b
Standardise terminology on OpenID Provider = OP
hughns d114f82
Update proposals/2964-oauth2-profile.md
hughns 8fc3ea1
Notes on QR and browserless
hughns 029e1e5
OpenID id_token endpoint is still needed
hughns 0802d8f
Notes about confusion with existing OIDC and OpenID capabilities
hughns 20ee4a3
Additional endpoints to be removed
hughns 6e387d8
Add 3pid endpoints that would be removed
hughns 4a2ed74
Changes to GET /account/3pid
hughns 40048da
Alternative proposal for 3PID handling
hughns f0e319a
Add section on removing UIA
hughns 55215c1
Refer to UIA as API
hughns 21fee1c
We now have proposal for 3PID and guest access
hughns 2c0625d
Logout semantics
hughns 24e0290
Remove TBDs that are done
hughns d145fd2
Merge branch 'msc/sandhose/oauth2-profile' of https://github.com/sand…
hughns acfa845
More done items
hughns fa506ff
Remove dependency loop
hughns 378348e
Merge branch 'matrix-org:main' into msc/sandhose/oauth2-profile
sandhose c859c0b
Rework proposal to only cover the authorization code flow
sandhose c1c8312
Fix a bunch of todos
sandhose 05748a2
Fix typos
sandhose 1034122
Fix the response_mode being an authorization request parameter
sandhose 4830d47
Apply suggestions from code review
sandhose f84428f
Remove unused images
sandhose File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 & | ||||||
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¶m2=value2` | ||||||
- if set to `query`, the parameters will be in placed the query string, like `https://example.com/callback?param1=value1¶m2=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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
URL encoding There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These values would be URL encoded
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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