From e8de1423b12c93cb53fcc4b1999aebc7369272da Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 8 Apr 2019 21:22:55 -0600 Subject: [PATCH 1/7] Proposal for OpenID information exchange with widgets --- proposals/0000-integrations-openid.md | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 proposals/0000-integrations-openid.md diff --git a/proposals/0000-integrations-openid.md b/proposals/0000-integrations-openid.md new file mode 100644 index 00000000..8658e828 --- /dev/null +++ b/proposals/0000-integrations-openid.md @@ -0,0 +1,83 @@ +# MSC0000: OpenID 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. + +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). + + +## Proposal + +Room and account widgets may request a new OpenID object 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 (riot-web#7153)](https://github.com/vector-im/riot-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 credentials from the user by sending a `fromWidget` action of `get_openid` +to intiate the token exchange process. The client should respond with an acknowledgement of +`{"state":"request"}` (or `{"state":"blocked"}` if the client/user doesn't think the widget is safe). +The client should then prompt 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 credentials +object returned from the homeserver, and a `success: true` parameter. If the user denies the widget, +just `success: false` is returned in the `data` property. To lessen the number of requests, a client may +also respond to the original `get_openid` request with a `state` of `"allowed"`, `success: true`, and +the OpenID object (just like in the data for `openid_credentials`). The widget should not request OpenID +credentials until after it has exchanged capabilities with the client, however this is not required. The +widget should acknowledge the `openid_credentials` request with an empty response object. + +A full sequence diagram for this flow is as follows: + +``` ++-------+ +---------+ +---------+ +| User | | Client | | Widget | ++-------+ +---------+ +---------+ + | | | + | | Capabilities negotiation | + | |<-----------------------------------------| + | | | + | | Capabilities negotiation | + | |----------------------------------------->| + | | | + | | fromWidget get_openid request | + | |<-----------------------------------------| + | | | + | | ack with state "request" | + | |----------------------------------------->| + | | | + | Ask if the widget can have information | | + |<--------------------------------------------| | + | | | + | Approve | | + |-------------------------------------------->| | + | | | + | | toWidget openid_credentials request | + | |----------------------------------------->| + | | | + | | acknowledge request (empty response) | + | |<-----------------------------------------| +``` + +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. + +A proof of concept for this system is demonstrated [here](https://github.com/matrix-org/matrix-react-sdk/pull/2781). + +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. + +## Security considerations + +The user is explicitly kept in the loop to avoid automatic and silent harvesting of private information. +Clients must ask the user for permission to send OpenID information to a widget, but may optionally allow +the user to always allow/deny the widget access. Clients are encouraged to notify the user when future +requests are automatically handled due to the user's prior selection (eg: an unobtrusive popup saying +"hey, your sticker picker asked for your information. [Block future requests]"). From bd0211be127c4807c3527583ea9556cd68473473 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 8 Apr 2019 21:24:57 -0600 Subject: [PATCH 2/7] Assign MSC number --- ...{0000-integrations-openid.md => 1960-integrations-openid.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename proposals/{0000-integrations-openid.md => 1960-integrations-openid.md} (99%) diff --git a/proposals/0000-integrations-openid.md b/proposals/1960-integrations-openid.md similarity index 99% rename from proposals/0000-integrations-openid.md rename to proposals/1960-integrations-openid.md index 8658e828..edc34a0b 100644 --- a/proposals/0000-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -1,4 +1,4 @@ -# MSC0000: OpenID information exchange for widgets +# MSC1960: OpenID 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 From 6490fda6eee63e8083584eb42184dd172d8b6fd6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 21 Aug 2020 07:07:41 -0600 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: David Baker Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> --- proposals/1960-integrations-openid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/1960-integrations-openid.md b/proposals/1960-integrations-openid.md index edc34a0b..9b2c981e 100644 --- a/proposals/1960-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -19,7 +19,7 @@ via a common widget - it would be nice if that widget could auth the user withou their username and password into an iframe. Widgets can request OpenID credentials from the user by sending a `fromWidget` action of `get_openid` -to intiate the token exchange process. The client should respond with an acknowledgement of +to initiate the token exchange process. The client should respond with an acknowledgement of `{"state":"request"}` (or `{"state":"blocked"}` if the client/user doesn't think the widget is safe). The client should then prompt 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 @@ -31,7 +31,7 @@ the OpenID object (just like in the data for `openid_credentials`). The widget s credentials until after it has exchanged capabilities with the client, however this is not required. The widget should acknowledge the `openid_credentials` request with an empty response object. -A full sequence diagram for this flow is as follows: +A successful sequence diagram for this flow is as follows: ``` +-------+ +---------+ +---------+ From 80214998f6af8ebd6daf2806c67bf99739da2098 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 21 Aug 2020 08:34:35 -0600 Subject: [PATCH 4/7] Various clarifications to structure --- proposals/1960-integrations-openid.md | 52 +++++++++++++++++++-------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/proposals/1960-integrations-openid.md b/proposals/1960-integrations-openid.md index 9b2c981e..edb6470a 100644 --- a/proposals/1960-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -1,4 +1,4 @@ -# MSC1960: OpenID information exchange for widgets +# 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 @@ -10,26 +10,50 @@ API proposed by [MSC1236](https://github.com/matrix-org/matrix-doc/issues/1236). ## Proposal -Room and account widgets may request a new OpenID object from the user so they can log in/register with +Room and account widgets may request new OpenID 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 (riot-web#7153)](https://github.com/vector-im/riot-web/issues/7153). The rationale for such an +[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 credentials from the user by sending a `fromWidget` action of `get_openid` -to initiate the token exchange process. The client should respond with an acknowledgement of +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 should then prompt the user if the widget should be allowed to get details about the user, +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 credentials -object returned from the homeserver, and a `success: true` parameter. If the user denies the widget, -just `success: false` is returned in the `data` property. To lessen the number of requests, a client may -also respond to the original `get_openid` request with a `state` of `"allowed"`, `success: true`, and -the OpenID object (just like in the data for `openid_credentials`). The widget should not request OpenID -credentials until after it has exchanged capabilities with the client, however this is not required. The -widget should acknowledge the `openid_credentials` request with an empty response object. +object returned from the homeserver, and a `success: true` parameter, similar to the following: +``` +{ + "api": "fromWidget", + "requestId": "AABBCC", + "action": "openid_credentials", + "widgetId": "DDEEFF", + "data": { + "success": true, + "access_token": "SecretTokenHere", + "token_type": "Bearer", + "matrix_server_name": "example.com", + "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`). + +The widget should not request OpenID credentials until after it has exchanged capabilities with the client, +however this is not required to wait for the capabiltiies exchange. + +The widget acknowledges the `openid_credentials` request with an empty response object. A successful sequence diagram for this flow is as follows: @@ -67,9 +91,9 @@ Prior to this proposal, widgets could use an undocumented `scalar_token` paramet 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. +storage techniques to avoid continously requesting credentials, regardless of how they got those credentials. -A proof of concept for this system is demonstrated [here](https://github.com/matrix-org/matrix-react-sdk/pull/2781). +An implementation of this proposal is [here](https://github.com/matrix-org/matrix-react-sdk/pull/2781). 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. @@ -77,7 +101,7 @@ the integration manager it is backed by to exchange it for a long-lived Bearer t ## Security considerations The user is explicitly kept in the loop to avoid automatic and silent harvesting of private information. -Clients must ask the user for permission to send OpenID information to a widget, but may optionally allow +Clients must ask the user for permission to send OpenID Connect information to a widget, but may optionally allow the user to always allow/deny the widget access. Clients are encouraged to notify the user when future requests are automatically handled due to the user's prior selection (eg: an unobtrusive popup saying "hey, your sticker picker asked for your information. [Block future requests]"). From 4cb7e96b110c5f14bbe5dc9deb50e2ff5d7635aa Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 27 Aug 2020 08:13:12 -0600 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> --- proposals/1960-integrations-openid.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/1960-integrations-openid.md b/proposals/1960-integrations-openid.md index edb6470a..6d6a7593 100644 --- a/proposals/1960-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -10,7 +10,7 @@ API proposed by [MSC1236](https://github.com/matrix-org/matrix-doc/issues/1236). ## Proposal -Room and account widgets may request new OpenID credentials from the user so they can log in/register with +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 @@ -18,12 +18,12 @@ to rely on secret strings. For example, a room could be used to let a user creat 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 credentials from the user by sending a `fromWidget` action of `get_openid` +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 credentials +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: ``` { @@ -50,7 +50,7 @@ To lessen the number of requests, a client can also respond to the original `get `state` of `"allowed"`, `success: true`, and the OpenID Connect credentials object (just like in the `data` for `openid_credentials`). -The widget should not request OpenID credentials until after it has exchanged capabilities with the client, +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. The widget acknowledges the `openid_credentials` request with an empty response object. From e304109289e6ae043677b0b81a1468f51c726991 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 27 Aug 2020 08:21:41 -0600 Subject: [PATCH 6/7] fix widget example --- proposals/1960-integrations-openid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/1960-integrations-openid.md b/proposals/1960-integrations-openid.md index 6d6a7593..84aa3c66 100644 --- a/proposals/1960-integrations-openid.md +++ b/proposals/1960-integrations-openid.md @@ -27,7 +27,7 @@ client sends a `toWidget` action of `openid_credentials` with `data` holding the object returned from the homeserver, and a `success: true` parameter, similar to the following: ``` { - "api": "fromWidget", + "api": "toWidget", "requestId": "AABBCC", "action": "openid_credentials", "widgetId": "DDEEFF", From c9e8326783366d95b7d564a78d6e4ab9f97ca03f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 5 Sep 2020 14:56:34 -0600 Subject: [PATCH 7/7] Reword following widget spec --- proposals/1960-integrations-openid.md | 177 +++++++++++++++++++------- 1 file changed, 131 insertions(+), 46 deletions(-) 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. +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). -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: -``` +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": "toWidget", - "requestId": "AABBCC", - "action": "openid_credentials", - "widgetId": "DDEEFF", - "data": { - "success": true, - "access_token": "SecretTokenHere", + "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.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. +```json +{ + "api": "fromWidget", + "action": "get_openid", + "requestId": "AAABBB", + "widgetId": "CCCDDD", + "data": {}, + "response": { + "state": "blocked" + } +} +``` -If the user denies the widget, just `success: false` is returned in the `data` property. +The credential information is directly copied from the `/_matrix/client/r0/user/:userId/openid/request_token` +response. -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`). +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. -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. +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: -The widget acknowledges the `openid_credentials` request with an empty response object. +```json +{ + "api": "toWidget", + "action": "openid_credentials", + "requestId": "EEEFFF", + "widgetId": "CCCDDD", + "data": { + "state": "allowed", + "original_request_id": "AAABBB", + "access_token": "s3cr3t", + "token_type": "Bearer", + "matrix_server_name": "example.org", + "expires_in": 3600 + } +} +``` -A successful sequence diagram for this flow is as follows: +```json +{ + "api": "toWidget", + "action": "openid_credentials", + "requestId": "EEEFFF", + "widgetId": "CCCDDD", + "data": { + "state": "blocked", + "original_request_id": "AAABBB" + } +} +``` + +`original_request_id` is the `requestId` of the `get_openid` request which started the prompt, +for the widget's reference. + +The widget acknowledges receipt of the credentials with an empty `response` object. + +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