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

Iframe-origin in the FedCM UX is sometimes not meaningful to users #449

Closed
yi-gu opened this issue Mar 27, 2023 · 14 comments
Closed

Iframe-origin in the FedCM UX is sometimes not meaningful to users #449

yi-gu opened this issue Mar 27, 2023 · 14 comments
Labels
enhancement New feature or request

Comments

@yi-gu
Copy link
Collaborator

yi-gu commented Mar 27, 2023

Some websites delegate authentication to a cross-origin iframe that they own. e.g. ebay.com embeds ebaystatic.com where FedCM API is invoked from. Showing the iframe domain unnecessarily confuses users because they trust eBay (the RP) and are not familiar with ebaystatic.com (the domain). e.g. a user will likely be confused when they see the following UI while visiting ebay.com

Ideally, the user shouldn’t need to have to care about the relationship between ebaystatic.com and ebay.com so the mock below makes more sense to the user.

Omitting the iframe domain makes sense in this case but not so much in other use cases. e.g. travel.example embeds a third-party booking.example where FedCM API is invoked. When the user is signing into booking.example (where the token is issued to) with idp.example, they are not signing in to travel.example. Therefore the string “sign in to travel.example with idp.example” when omitting the iframe domain is incorrect and showing all 3 domains on the UI to provide users the complete information is better.

We should have a way to differentiate between the two use cases.
@yi-gu
Copy link
Collaborator Author

yi-gu commented Apr 20, 2023

Note
Strings on the FedCM UI are not supposed to be standardized and different browsers may have different criteria on what has to be shown on the UI. This proposal does not intend to normalize the strings, rather, it proposes a way for browsers to differentiate between the two use cases mentioned in this issue and present meaningful UI to users accordingly if desired. Given that the issue may not apply to all browsers the proposal is optional to both developers and UAs.

For this proposal, let top frame origin be https://top-frame.example/, RP iframe origin be https://iframe.example and IDP origin be https://idp.example. top-frame.example embeds iframe.example with proper permissions policy that allows the iframe to call FedCM API.

Matched clients

Any application that uses OAuth 2.0 must have authorization credentials i.e. a client ID which identifies the application to the IdP server. When a top frame has the same client ID as the iframe (RP) that calls FedCM API, the top frame is considered a matched client to the RP. In this case showing both the top frame domain and iframe domain can unnecessarily confuse users without gaining privacy/security benefits. Given that the top frame domain is the one that users see in the URL bar, omitting the iframe domain could lead to better UX.

Note that “client ID” is just one possible way to tell whether the top frame is a matched client to the RP iframe, IdPs may have other mechanisms to check it. e.g. same-site frames could also be a factor to some IdPs.

How to use matched clients

The proposal is to use this as an indication for whether we should only show two domains (top frame and IdP) or show all three domains (top frame, iframe and IdP).

How to determine matched clients

The browser currently sends the iframe origin as the Origin in the fetch to an IdP’s client metadata endpoint. The proposal is to include the top frame origin in the fetch, i.e. in the body of the GET request:

GET /client_metadata.php?client_id=1234&top_frame_origin=https://top-frame.example HTTP/1.1
Host: idp.example
Origin: https://iframe.example/
Accept: application/json
Sec-Fetch-Dest: webidentity

This would provide the IdP sufficient information to check whether the clientID matches not only the Origin header (which the IdP should always do) and hence the child frame origin, but also the top frame origin. e.g. when an RP registered two different origins or domains for themselves, they should be issued the same client ID by the IdP if those domains are for the same RP. Then the IdP can look up the two origins to check if they belong to the same RP.

Then in the client metadata fetch response, the IDP would specify an optional boolean parameter client_matches_top_frame_origin:

{
  "privacy_policy_url": "https://rp.example/privacy_policy.html",
  "terms_of_service_url": "https://rp.example/terms_of_service.html",
  "client_matches_top_frame_origin": true  
}

And the browser can use client_matches_top_frame_origin to determine whether to show two domains or three domains. Note that if client_matches_top_frame_origin is not specified, the default value is false to err on the side of caution.

Security and privacy considerations

Technically speaking this proposal provides a new way for an embedded iframe to learn the origin of the top frame. In particular, an iframe could call FedCM with their own fake IdP that receives both the iframe origin and the top frame origin. However, for a cross-origin iframe to call FedCM API, by design the top frame must allow it explicitly by specifying the permissions policy “identity-credentials-get”.

It is already possible today in some browsers for an iframe to learn the top frame’s origin via ancestorOrigins. While Firefox has concerns about ancestorOrigins, one key difference between this proposal and ancestorOrigins is the permissions policy requirement. i.e. by design, for the iframe to learn the top frame’s origin via FedCM, all the frames on the embedding chain must opt in (trust the embedded iframe) by specifying the permissions policy “identity-credentials-get”. This could make a difference according to this comment.

Another new entropy this proposal adds is that it technically provides a way for anyone to query the association between two origins with a specific client id using curl. e.g. https://foo.example/ registered themselves to an IdP with client id: client_foo. This information is public. Then someone can use curl to send a request to the client_metadata endpoint with that information plus top_frame_origin=https://bar.example/. The IdP would then let the requestor know whether bar.example is a matched client to foo.example. That being said, technically speaking the information which the requester can obtain should also be publicly available. In addition, this can be achieved without this proposal by sending two separate requests to the endpoint with the two origins and comparing the responses.

From Chrome privacy’s perspective, we believe that showing less confusing UIs could help users to make more meaningful decisions when prompted to continue with FedCM.

Technically, the IdP can lie about the new boolean. However, omitting the iframe domain when it’s not supposed to could put the IdP at risk because it’s the iframe who receives the token with sensitive information. For legitimate IdPs this is definitely not in their interest. For bad acting IdPs, they have to collude with the top frame to explicitly use the permissions policy to use FedCM, but in case of collusion, the bad actor does not need to use an iframe at all.

Interoperability and compatibility considerations

As noted in the beginning, this issue may not be browser-agnostic because it’s a string issue on the browser UI. For browsers who don’t have this issue, the proposal is completely optional and backwards compatible. For browsers who share the same issue, we believe that the proposal is designed in an interoperable manner.

Considered alternatives

Check matched client from the browser side

Instead of the browser sending both the top frame origin and iframe origin to the IdP, the IdP can return a list of origins that are “matched” in the client metadata fetch response. Then the browser can try matching the top frame origin from that list to see if it’s matched. However, there are some drawbacks of this alternative:

  • IdP needs to expose information that is otherwise unavailable to all requesters. Hashing the returned origins may help to some extent, but it may still raise a red flag for IdPs given the sensitivity of the information.
  • IdP may have different criteria to determine “matched clients”. e.g. two domains that are cross-origin but same-site (https://foo.rp.example and https://bar.rp.example) could be considered as “matched clients” to each other. Asking IdP to return a full list of such origins is suboptimal.

Using different permissions policies

We could introduce two different permissions policies for FedCM in cross-origin iframes: “identity-credentials-get-include-iframe-domain” and “identity-credentials-get-omit-iframe-domain” to give the control to the top frame. However there are some drawbacks:

  • If a top frame chooses to omit the iframe domain when it’s not supposed to, users would make decisions based on incomplete information and IdP may expose the token to the iframe which could be a bad actor that’s different from the top frame.
  • API confusion. This puts an extra burden on the top frame to use permissions policy properly.
  • Typically how to build browser UI is not standardized. Adding a new API to control the string is undesirable.

@yi-gu
Copy link
Collaborator Author

yi-gu commented Apr 20, 2023

@martinthomson @cboozar @bvandersloot-mozilla
From Chrome's perspective, we do want to differentiate the two use cases to prompt proper UI to users. Not sure whether Firefox has the same confusing UI issue when FedCM is used in a cross-origin iframe.

  • If so, for this proposal, is our understanding of Firefox's position on ancestorOrigins correct? In particular, is the permissions policy requirement sufficient to reveal the top frame origin (just top frame, not the other nested iframes if any) to the iframe who uses FedCM API?
  • if not, do you think this proposal can be spec'd in a reasonable manner?

@yi-gu yi-gu added agenda+ Regular CG meeting agenda items enhancement New feature or request labels Apr 26, 2023
@philsmart
Copy link
Contributor

To me, it seems confusing to say, 'the clientID matches not only the Origin header...', the client_id can be any string, not necessarily related to an origin. From the later text, it seems client_id is used to locate registered origins related to a client (something equivalent to what Google stores about OAuth2 clients, namely; 'Authorised JavaScript origins'). Maybe that could be clarified in the text (if I am not wrong about it).

I would not imagine every OAuth2 (or OIDC etc) authorization server would store an equivalent to 'Authorised JavaScript origins'. I do not think it is part of any OAuth2/OIDC registration spec? This would mean this would need to be specified as a FedCM registration extension for it to be widely used.

@yi-gu
Copy link
Collaborator Author

yi-gu commented May 1, 2023

To me, it seems confusing to say, 'the clientID matches not only the Origin header...', the client_id can be any string, not necessarily related to an origin.

Totally agree. We deliberately chose the phrase "client matches top" just to give IdPs more flexibility to determine "match". It does not necessarily get narrowed down to using "client ID". e.g. client ID may not needed at all in the process because IdP can look up the two origins and determine if the browser can skip showing one of them.

@npm1
Copy link
Collaborator

npm1 commented May 2, 2023

On the call (notes), a suggestion was made to consider using First Party Sets (FPS) to solve this problem. That is, the user agent would check whether the iframe and the top-level embedder are members of the same FPS to determine whether to show two or three domains in the UI. The benefit of this would be that it would not require introducing a new API. With FPS now shipping in Chrome, Chrome could use FPS as the solution. We took the AI to ask FPS folks what they think and the consensus seems to be leaning towards not using FPS for the following reasons:

  • It would mean integrating FedCM with a controversial API, in particular one that Firefox and Webkit aren’t favorable of
  • The origins would need to publicly share their relationship as part of a FPS, which some RPs might be concerned about
  • Requires non trivial work from RPs compared to the proposed solution that puts the work onto IDPS

Tagging @johannhof @krgovind in case they want to share their thoughts from the FPS side!

@krgovind
Copy link

krgovind commented May 2, 2023

Thanks for summarizing, @npm1! I agree with your assessment.

Interoperability should definitely be a consideration here. The only other proposed integration of FPS on the platform today is with the requestStorageAccess and requestStorageAccessFor APIs, and browsers that don't support FPS have their own handling of those API calls. For example, Firefox currently auto-grants the first five invocations of requestStorageAccess for a given 3p site, and prompts users beyond that. Safari always prompts. (requestStorageAccessFor is a new proposal, and still under discussion).

Since it appears that RPs already register their list of related sites with their trusted IDPs; I believe the purpose that FPS serves for the cross-site cookie access use-case (i.e. provide a platform mechanism for site authors to declare collections of related sites) is already superseded by that existing mechanism. It makes sense to leverage that.

@martinthomson
Copy link
Contributor

(This all assumes cross-site cookie blocking or isolation.)

I'm happy with the conclusion about FPS, though I had a shorter path to reach it :)

I think that the model this should use is the one we adopted for permissions policy, which this API already uses. That is, if a top-level context wants to delegate permission, then it can do so. But the effect of that is that they are responsible for the effects of that. So maybe this is really api.mallacoota.example.net asking rather than example.com, but the user doesn't need to care about that distinction. The information is transferring to the example.com partition of browser state and that is what really matters, not who they contract with to manage a given component of their site.

The challenge then is the accountability at the IdP end. The IdP will report access from some obscure site, which isn't ideal. So, we can tell them the origin from the top-level context. Then they can report that. We can make the automated availability of that datum a non-negotiable part of the contract when the top-level site allows the use of this API in a given frame. There is no privacy risk there because sites can already arrange to make that information available if they chose to.

The proposed design pushes that work back on sites, which is annoying for them and provides no privacy advantage. So my suggestion is that we decorate these fetches as follows (precise spelling TBN):

Origin: api.mallacoota.example.net
Top-Origin: example.com

You might notice that this has more general applicability. We don't do this now, so we probably shouldn't start without sites asking us to, but we can definitely do that here.

(This idea of matched clients using client ID makes no sense to me. That two sites might have the same client ID with a given IdP is not information that a browser can - or should - be making decisions based on.)

@johannhof
Copy link
Contributor

I generally agree with Martin that using the permission delegation pattern (i.e. showing only the top-level) here is a good default for FedCM. In most cases it's most intuitive for the user and most correct from a privacy perspective, as delegation ensures collaboration between the top-level and the embed.

But I can also see the point that this is confusing to users in some situations. However, both FPS and the client ID idea seem a bit too... indirect for my taste, deriving this kind of UI based on tangential relationships between sites can be hit-and-miss. It seems like the embedded frame should have enough context to decide whether to show this UI or not on its own, no? Is there some way we can delegate this decision to the API caller, like an argument or a configuration somewhere?

The additional header comment seems reasonable as well but I haven't spent a lot of time thinking about it :)

@npm1
Copy link
Collaborator

npm1 commented May 5, 2023

Thanks for the feedback, @martinthomson! I do agree that it is a bit awkward for the user agent to trust the IDP with the task of attesting that two origins are the same client. That said, having a Permissions Policy be the solution for this is not something we would be supportive of because it is possible for an attacker to grant itself permissions via the HTML injection attack. This was one of our motivations for this feature, as well as for displaying three origins by default instead of two.

That said, @RByers suggested that perhaps we can just change the solution slightly so that JavaScript must be called in order for the embedder to grant permission. That is, we'd have a JS call that the embedder needs to call to grant its iframe permission to use FedCM. This could be in addition to or in replacement of the existing API:

  • In addition to: the JS call lets the iframe's FedCM prompt display only two origins.
  • In replacement of: instead of using Permissions Policy as a framework to grant access, we rely solely on this API call, and the prompt displays only two origins.

What do you think? We're also waiting for @yi-gu to come back from vacation as he's been driving this API and will have thoughts :)

@martinthomson
Copy link
Contributor

martinthomson commented May 7, 2023

HTML injection is why we have CSP. I'm not convinced that that motivates poor UX.

This permission delegation attack (with script access, many things are possible) is real, but I don't think that it is our business to come up with a solution to it here.

@yi-gu
Copy link
Collaborator Author

yi-gu commented May 8, 2023

Some clarifications to make sure that folks are on the same page:

The security issue

It's true that CSP or strict CSP can disable Javascript access for an HTML injection attacker. However, it does not forfeit the ability to run Javascript in a cross-origin iframe. e.g. an attacker can inject <iframe src="https://evil.example" allow="identity-credentials-get"> and then call the FedCM API from the iframe. To our best knowledge, CSP doesn't help in this case. If the attacker can somehow omit the iframe domain "evil.example" on the UI, that would lead to severe problems.

The victims

The main victims in this type of attack are users and IdPs as oppose to the embedder site. e.g. a well recognized website got attacked. Without a proper browser UI, e.g. omitting the iframe domain "evil.example", a user will see "Sign in to well-recognized-website.example with idp.example". Since the user is visiting the website and trusts both the top frame and the IdP, it's likely that they will click the "Continue" button on the FedCM UI. The consequence of that action is that the sensitive data (e.g. an id token) will be shared with the attacker without user/IdP knowing it.

Considered solutions

Option 1: Multiple permission policies

This was the first solution that came to us for this Github issue. e.g. the top frame could use <iframe allow="identity-credentials-get-show-iframe"> and <iframe allow="identity-credentials-get-omit-iframe"> respectively to delegate the permission. However, due to the HTML injection mentioned above, an attacker can just choose <iframe allow="identity-credentials-get-omit-iframe"> on behalf of the top frame. So this won't work.

Option 2: Enforce header policy

We could ask websites that want to support FedCM in cross-origin iframes to use the header: Permissions-Policy: identity-credentials-get=(allowlist of origins). It's possible for UAs to ignore any allow="identity-credentials-get" container policy when the header policy is present.

There are some concerns:

  1. It doesn't follow the typical pattern of Permissions Policy and will add extra implementation cost for websites
  2. It significantly increases the deployment cost for "personalized buttons". In short, personalized buttons are IdP iframes embedded on a top frame. To use FedCM to render the buttons, with header policy, there will be RP code change needed even if an IdP SDK is loaded.
  3. It may require implementation beyond RPs (e.g. top frame: travel-hub.example; iframe/RP: car-rental.example. Here the top frame shouldn't care about how to use FedCM because they are not the RP and will not receive the id token).

Option 3 Enforce Permissions Policy via JavaScript

As mentioned in the last comment, we can support FedCM in cross-origin iframes if and only if the permissions policy is set by Javascript (which is protected by CSP in case of HTML injection).

Some common concerns shared with the previous option:

  1. It doesn't follow the typical pattern of Permissions Policy and will add extra implementation cost for websites. e.g. for RPs who use FedCM without IdP SDKs, now they need to remember to use JavaScript to set FedCM related permission policy while there's no such requirement for other policies.
  2. While the permission delegation attack fix is orthogonal, when it's in place in the future, both enforcements from option 2 and 3 become less useful and lead to extra maintenance cost for UAs in addition to the developer confusion.

Option 4: no dependency on the website (this proposal)

Note that all the options listed above are similar in a way that they all require the top frame to do the right thing (using proper policy/header/JavaScript) while the top frame may not even be the RP that receives the token. On the other hand, the cost of them NOT doing the right thing would be paid by users and IdPs. e.g. if UA always shows 3 domains (top frame, iframe, IdP), users will be confused; if UA omits iframe domain improperly (due to top frame's mistake), users may grant permission unintentionally and IdP would leak the sensitive user information unexpectedly.

That's why we proposed to solve this problem from UA level instead of the website level. Because UA knows about the origin that the user is visiting and the origin that calls FedCM, it can provide the information accurately to the potential victim IdP such that the IdP could speak up to protect their users.

Some extra notes:

  • We never intent to use "client id" as the source of truth for "matched clients" (sorry for the confusion and we also clarified that in FedIdCG). As a matter of fact, how IdPs will determine whether the two origins (top, iframe) belong to matched clients is opaque to UA. All that matters to the UA is the boolean returned from the IdP in the client metadata request response.
  • Glad to hear that Firefox is OK to expose the top frame origin to the IdP! As for the option to expose it via a new Top-Origin header, agreed that it has more general applicability. That said, "with great power comes great responsibility". Will the community be convinced to introduce such a new forbidden header with the current use case? Especially it could be achieved by using the proposed query parameter. It's definitely a promising direction to pursue to unify all possible use cases if any. That said, it might take some non trivial amount of time to land. Does it make sense if we use the parameter top_frame_origin in the spec now and leave a note to indicate the possible direction of using Top-Origin?

@martinthomson
Copy link
Contributor

Option 5: open an issue on the permissions policy repository and solve the problem for everyone and for every feature for which this might be is a problem. A CSP directive is my preferred approach.

@RByers
Copy link

RByers commented May 16, 2023

Thanks @martinthomson, FWIW I agree 100% with your perspective here. Yi has looped me into the discussion with our security teams and we'll follow up here once we have a conclusion from that.

@yi-gu
Copy link
Collaborator Author

yi-gu commented May 25, 2023

Hi folks. To report back, here are the updated proposal after some further discussions:

  1. As @martinthomson pointed out, the solution to the HTML injection problem is CSP
  2. The potential issue with permission delegation applies to all relevant features and FedCM should not be special-cased (or introduce new APIs that are inconsistent with the existing use of Permissions Policy)
  3. The initial UI confusion issue raised here should be orthogonal to Update README.md #1 and Small grammar fix. #2.

At the moment we propose to keep the FedCM API as-is and leave the exact UI to browsers. Given that the policy identity-credentials-get is enforced to use FedCM in cross-origin iframes, the top frame should be aware of that. In the future if we hear clear feedback from the community (e.g. top frame needs more control on whether to show their embeddees), we can revisit the issue and go from there.

Closing this issue for now and feel free to reopen it if you have further concerns or questions.

@yi-gu yi-gu closed this as completed May 25, 2023
@yi-gu yi-gu removed the agenda+ Regular CG meeting agenda items label May 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants