diff --git a/proposals/1960-integrations-openid.md b/proposals/1960-integrations-openid.md index 84aa3c66..6f33001f 100644 --- a/proposals/1960-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -1,61 +1,144 @@ # MSC1960: OpenID Connect information exchange for widgets -With the various integrations API proposals, widgets are left with no options to verify the -requesting user's ID if they need it. Widgets like the sticker picker must know who is making -the request and as such need a way to get accurate information about who is contacting them. +Widgets are currently left with no options to verify the user's ID, making it hard for +personalized and authenticated widgets to exist. The spec says the `$matrix_user_id` +template variable cannot be relied upon due to how easy it is to faslify, which is true. -This proposal introduces a way for widgets (room and account) to do so over the `fromWidget` -API proposed by [MSC1236](https://github.com/matrix-org/matrix-doc/issues/1236). +This MSC aims to solve the problem with verifiably accurate OpenID Connect credentials. +As of writing, the best resource to learn more about the widgets spec is the following +spec PR: https://github.com/matrix-org/matrix-doc/pull/2764 ## Proposal -Room and account widgets may request new OpenID Connect credentials from the user so they can log in/register with -the backing integration manager or other application. This is largely based on the prior art available -[here (element-web#7153)](https://github.com/vector-im/element-web/issues/7153). The rationale for such an -API is so that widgets can load things like a user's sticker packs or other information without having -to rely on secret strings. For example, a room could be used to let a user create custom sticker packs -via a common widget - it would be nice if that widget could auth the user without asking them to enter -their username and password into an iframe. - -Widgets can request OpenID Connect credentials from the user by sending a `fromWidget` action of `get_openid` -to initiate the token exchange process. The client responds with an acknowledgement of -`{"state":"request"}` (or `{"state":"blocked"}` if the client/user doesn't think the widget is safe). -The client then prompts the user if the widget should be allowed to get details about the user, -optionally providing a way for the user to always accept/deny the widget. If the user agrees, the -client sends a `toWidget` action of `openid_credentials` with `data` holding the raw OpenID Connect credentials -object returned from the homeserver, and a `success: true` parameter, similar to the following: +Typically widgets which need to accurately verify the user's identity will also have a +backend service of some kind. This backend service likely already uses the integration +manager authentication APIs introduced by [MSC1961](https://github.com/matrix-org/matrix-doc/pull/1961). + +Through using the same concepts from MSC1961, the widget can verify the user's identity +by requesting a fresh OpenID Connect credential object to pass along to its backend, like +the integration manager which might be running it. + +The protocol sequence defined here is based upon the previous discussion in the Element Web +issue tracker: https://github.com/vector-im/element-web/issues/7153 + +It is proposed that after the capabilities negotation, the widget can ask the client for +an OpenID Connect credential object so it can pass it along to its backend for validation. +The request SHOULD result in the user being prompted to confirm that the widget can have +their information. Because of this user interaction, it's not always possible for the user +to complete the approval within the 10 second suggested timeout by the widget spec. As +such, the initial request by the widget can have one of three states: + +1. The client indicates that the user is being prompted (to be followed up on). +2. The client sends over credentials for the widget to verify. +3. The client indicates the request was blocked/denied. + +The initial request from the widget looks as follows: + +```json +{ + "api": "fromWidget", + "action": "get_openid", + "requestId": "AAABBB", + "widgetId": "CCCDDD", + "data": {} +} +``` + +Which then receives a response which has a `state` field alongside potentially the credentials +to be verified. Matching the order of possible responses above, here are examples: + +```json +{ + "api": "fromWidget", + "action": "get_openid", + "requestId": "AAABBB", + "widgetId": "CCCDDD", + "data": {}, + "response": { + "state": "request" + } +} ``` + +```json +{ + "api": "fromWidget", + "action": "get_openid", + "requestId": "AAABBB", + "widgetId": "CCCDDD", + "data": {}, + "response": { + "state": "allowed", + "access_token": "s3cr3t", + "token_type": "Bearer", + "matrix_server_name": "example.org", + "expires_in": 3600 + } +} +``` + +```json +{ + "api": "fromWidget", + "action": "get_openid", + "requestId": "AAABBB", + "widgetId": "CCCDDD", + "data": {}, + "response": { + "state": "blocked" + } +} +``` + +The credential information is directly copied from the `/_matrix/client/r0/user/:userId/openid/request_token` +response. + +In the case of `state: "request"`, the user is being asked to approve the widget's attempt to +verify their identity. To ensure that future requests are quicker, clients are encouraged to +include a "remember this widget" option to make use of the immediate `state: "allowed"` or +`state: "blocked"` responses above. + +There is no timeout associated with the user making their selection. Once a user does make +a selection (allow or deny the request), the client sends a `toWidget` request to indicate the +result, using a very similar structure to the above immediate responses: + +```json { "api": "toWidget", - "requestId": "AABBCC", "action": "openid_credentials", - "widgetId": "DDEEFF", + "requestId": "EEEFFF", + "widgetId": "CCCDDD", "data": { - "success": true, - "access_token": "SecretTokenHere", + "state": "allowed", + "original_request_id": "AAABBB", + "access_token": "s3cr3t", "token_type": "Bearer", - "matrix_server_name": "example.com", + "matrix_server_name": "example.org", "expires_in": 3600 } } ``` -For clarity, the `data` consists of properties as returned by `/_matrix/client/r0/user/:userId/openid/request_token` -plus the `success` parameter. - -If the user denies the widget, just `success: false` is returned in the `data` property. - -To lessen the number of requests, a client can also respond to the original `get_openid` request with a -`state` of `"allowed"`, `success: true`, and the OpenID Connect credentials object (just like in the `data` for -`openid_credentials`). +```json +{ + "api": "toWidget", + "action": "openid_credentials", + "requestId": "EEEFFF", + "widgetId": "CCCDDD", + "data": { + "state": "blocked", + "original_request_id": "AAABBB" + } +} +``` -The widget should not request OpenID Connect credentials until after it has exchanged capabilities with the client, -however this is not required to wait for the capabiltiies exchange. +`original_request_id` is the `requestId` of the `get_openid` request which started the prompt, +for the widget's reference. -The widget acknowledges the `openid_credentials` request with an empty response object. +The widget acknowledges receipt of the credentials with an empty `response` object. -A successful sequence diagram for this flow is as follows: +A typical sequence diagram for this flow is as follows: ``` +-------+ +---------+ +---------+ @@ -63,10 +146,10 @@ A successful sequence diagram for this flow is as follows: +-------+ +---------+ +---------+ | | | | | Capabilities negotiation | - | |<-----------------------------------------| + | |----------------------------------------->| | | | | | Capabilities negotiation | - | |----------------------------------------->| + | |<-----------------------------------------| | | | | | fromWidget get_openid request | | |<-----------------------------------------| @@ -89,14 +172,16 @@ A successful sequence diagram for this flow is as follows: Prior to this proposal, widgets could use an undocumented `scalar_token` parameter if the client chose to send it to the widget. Clients typically chose to send it if the widget's URL matched a whitelist for URLs -the client trusts. Widgets are now not able to rely on this behaviour with this proposal, although clients -may wish to still support it until adoption is complete. Widgets may wish to look into cookies and other -storage techniques to avoid continously requesting credentials, regardless of how they got those credentials. +the client trusts. With the widget specification as written, widgets cannot rely on this behaviour. -An implementation of this proposal is [here](https://github.com/matrix-org/matrix-react-sdk/pull/2781). +Widgets may wish to look into cookies and other storage techniques to avoid continously requesting +credentials. Widgets should also look into [MSC1961](https://github.com/matrix-org/matrix-doc/pull/1961) +for information on how to properly verify the OpenID Connect credentials it will be receiving. The +widget is ultimately responsible for how it deals with the credentials, though the author recommends +handing it off to an integration manager's `/register` endpoint to acquire a single token string +instead. -The widget is left responsible for dealing with the OpenID object it receives, likely handing it off to -the integration manager it is backed by to exchange it for a long-lived Bearer token. +An implementation of this proposal's early draft is here: https://github.com/matrix-org/matrix-react-sdk/pull/2781 ## Security considerations