-
Notifications
You must be signed in to change notification settings - Fork 73
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
Authorizing non-profile oauth scopes #477
Comments
One proposal, is to extend the FedCM JS API (with a parameter called “scope”) to introduce a signal that the user agent can use to distinguish whether to mediate or delegate authorization. When it delegates authorization, after the existing browser mediated account chooser, the user agent uses a pop-up window to load content from the IdP that can be used to gather the user’s authorization. The existing browser mediated account chooser allows the browser to capture the user’s explicit confirmation / acknowledgement that the user’s intention is to sign-in/sign-up/continue to a relying party with an identity provider. This prevents a tracker from abusing this API, and maintains the privacy bar that FedCM has set. The delegated IdP window allows the IdP to gather the user’s permission using its own words. Architecturally, this is accomplished by the following algorithm changes to the FedCM execution: Specifically, we propose to:
User FlowsIn this proposal, our journey starts with a “sign-in with idp.com” button that is rendered by websites, in this case www.timeoff.com. From this interaction, the website can use a newly introduced parameter to the FedCM API, called scopes, which is an array of opaque strings, to trigger the new flow: partial dictionary IdentityProviderConfig {
// A list of strings that represent what the Relying Party needs
// from the Identity Provider.
sequence<USVString> scope;
// The types of tokens that the RP wants to get back from the IdP.
// More than one can be requested, e.g. an id_token and an access code
// token:
// https://openid.net/specs/openid-connect-core-1_0.html
// #code-id_token-tokenExample
sequence<USVString> responseType;
// A map of key-value pairs that are opaque to the browser and only
// passed to the IdP after the user's acknowledgement.
record<USVString, USVString> params;
}; For example: let {token} = await navigator.credentials.get({
identity: {
providers: [{
// A newly introduced "scope" attribute, a list of strings
// Defaults to "name email photo"
scope: [
"drive.readonly",
"calendar.readonly",
],
clientId: "1234",
nonce: "234234",
loginHint: "[email protected]",
configURL: "https://idp.example/",
responseType: ["id_token"],
// A string with parameters that need to be passed from the
// RP to the IdP but that don't really play any role with
// the browser.
params: {
"IDP_SPECIFIC_PARAM": "1",
"foo": "BAR",
"ETC": "MOAR",
}
},
}
// If possible, return without prompting the user, if not possible
// prompt the user.
mediation: "optional",
}); With this newly introduced parameter, the browser then knows that it should not mediate authorization but rather delegate it. It starts by going through the mediated FedCM account choosing flow, which can be customized for this specific case (e.g. we can show it as a modal dialog, etc). The (modified) account chooser serves to gather the user’s acknowledgement that the intent is to “sign-in to website.com with idp.com”. With the user’s permission, (a) the browser gets confirmation that this was indeed the user’s intent and (b) introduces to the IdP the user’s intent to sign-in to the relying party for the first time. After account choosing, based on the JS request scope parameter, the browser makes a determination: should the browser mediate the authorization for exchanging the user’s profile (is the RP asking only for the user’s profile?) or delegate authorization to the IdP (is the RP asking for OAuth scopes that the browser wouldn’t know how to formulate a question to the user?). If the RP needs access to OAuth APIs, the browser makes an HTTP POST request to the IdP’s id_assertion_endpoint with all of the parameters passed by the RP. The IdP, with all of the information available (request parameters, etc), can make a determination on whether it has gathered sufficient acknowledgement from the user or not and it uses the response of the POST request to determine how the browser should proceed: if the response provided by the IdP resolves with a “token” parameter, the browser continues to resolve the promise without any extra necessary permissions (as per the usual FedCM flow), but, in the presence of a “continue_on” parameter in the response, the browser is commanded to ask the user for more information and opens a pop-up window with the value of the “continue_on” parameter. For example, when the IdP gets the following POST request to id_assertion_endpoint:
The IdP can now respond with: {
// In the id_assertion_endpoint, instead of returning a typical
// "token" response, the IdP decides that it needs the user to
// continue on pop-up window:
// "token" : "eyJC...J9.eyJzdWTE2...MjM5MDIyfQ.SflV_adQssw....5c"
"continue_on": "/oauth/authorize?scope=..."
} With that response from IdP back, because the browser acknowledges that it doesn’t have the words to ask the user the question (i.e. the browser doesn’t know what the “scope” is), it delegates that task to the IdP by opening a pop-up window (the actual UX is user agent specific) with the right attribution (i.e. it shows the origin/url that is being served) by opening a pop-up window pointing the user to the “continue_on” URL. Because the browser has already gathered the user’s consent to release to the IdP who the RP is, it includes both the IdP’s first party cookies as well as the RP’s identity, which is then able to construct the necessary permission prompt using its own language: When the IdP has gathered sufficient confirmation from the user that the operation is authorized, it calls a newly introduced JS API that tells the browser to hand it back to the RP (which the browser does by resolving the original promise). // NOTE: this is only callable from the IDP's top level frame.
interface IdentityProvider {
// Provides the token back to the Relying Party.
static void resolve(token);
}; For example: document.getElementById("allow").addEventListener("click", async () => {
let accessToken = await fetch(`/generate_access_token.cgi`);
// Closes the window and resolves the promise (that is still hanging
// in the relying party's renderer) with the value that is passed.
IdentityProvider.resolve(accessToken);
// Closes the window.
window.close();
}); With the access token back at the RP, and with the browser’s knowledge that the user has consented to signing-in to the RP with the IdP, the browser then has more context on the communication between the IdP and the RP. For example, when a user returns to the relying party, the relying party has a choice of calling the FedCM API in “silent” mode, which refreshes the session if one was already granted, or fails otherwise: let result = await navigator.credentials.get({
// Returns a result without any UI in case it is possible
// (specifically, only after the user has consented to the FedCM
// prompt in the past). Otherwise, returns an error.
mediation: "silent",
identity: {
providers: [{
// newly introduced "scope" attribute, a list of strings
scope: [
"google-drive.readonly"
],
configURL: "https://idp.example/",
clientId: "1234",
}]
}
}); Which leads to the end of the user journey, signed-in to the website and having given the website all that it needs to operate. Alternatives ConsideredThe most notable alternatives are on the opposite side of the spectrum:
Which are covered in the next subsections. Mediated API AuthZThe most immediate idea that came to mind was to fully mediate the authorization flow. While that’s not entirely impossible, and shouldn’t be discarded, we quickly had an intuition that this approach would lead to (a) substantial amount of work to make it expressive enough within (e.g. a single IdP is already really complex) and across Identity Providers (i.e. make it representative of all of the IdPs product requirements) for (b) zero to no privacy/security benefits (e.g. with regards to keeping the privacy tracking bar) and, importantly, (c) ossification of the UI in the form of lack of extensibility (e.g. hold back the ecosystem to innovate). Nonetheless, in this alternative, the browser would come up with a protocol to “get strings from IdPs” that can be embedded into templates in the browser UI. For example, in the following UI, the string “Calendar” is provided by the IdP (via resolving the scope parameter), but everything else is browser mediated. While it is true that this could potentially make the browser more confident that the user’s consent was more explicit (the browser does control more of the UI, even though it does not know for sure what “Calendar” means – because that’s an IdP-specific concept), it suffers from a variety of extensibility and expressivity problems. We haven’t ruled out this alternative, but we think the current proposal strikes a much better balance. Existing PrimitivesThe second noteworthy alternative is to implement AuthZ entirely in userland with the existing primitives that are available, namely, window.open(). What’s most compelling about this alternative is that we’d reuse a lot of the existing APIs and not have to rethink the privacy/security properties of the new API surface. So, given the choice, this could be compelling because it would be the least amount of work that browsers would have to do. In this formulation, it would be the website’s responsibility to request AuthZ after having asked for AuthN: async function authZ() {
return new Promise((resolve) => {
const {token} = await navigator.credentials.get({
identity: {
providers: [{
// No extra parameters (e.g. scopes) to AuthN introduced.
configURL: "https://idp.example/",
clientId: "1234",
}]
}
});
// AuthN gathered, kick off the AuthZ flow now
// 1) Opens a pop-up window
const popup = window.open("[https://idp.example/authz](https://idp.example/authz?scopes=google-drive)");
// 2) Requests an "age" assertion from it
popup.postMessage("/request?scope=age");
// 3) When the user accepts, resolve the promise
window.addEventListener("message", ({data}) => {
// Gets the access token that was given back
resolve(data);
});
});
} While not something that we have entirely ruled out yet, this example shows some of the challenges: in the current UX flows, the RP only gets the response AFTER the use goes through the AuthN AND the AuthZ flow, whereas here, the RP would get “a” response right after the AuthN flow but BEFORE the AuthZ flow, and then “another” response after the AuthZ flow. Some of this can be mitigated by perhaps only allowing the IdP to call the API in this fashion (e.g. only allow this to be called from an iframe), but it raises some of these questions. ExtensibilityInterestingly, because the IdP gets to ask for the user’s permission using their own words (i.e. their own HTML/JS/CSS), the browser is entirely blind about what and how and why it is being asked. We think that’s more of a feature than a bug. For example, there are IdPs that could choose to ask for the parent’s user’s permission for under aged users (even when only the user’s profile is shared), and could lead the under aged user to pass the phone to their parents, which is something that would be close to impossible for a mediated flow to anticipate. We think that having space in which IdPs can innovate independently without being pulled back by browser vendors can give a massive amount of autonomy to the ecosystem. Open QuestionsThere are at least a few corner cases worth noting here: (a) we expect most users to have a single account (as opposed to multiple), so we need to make sure that the account chooser isn’t awkward in that case, (b) we need to deal with the case that the user is signed-out of the IdP and needs to be able to sign-in and (c) we need to introduce the ability for the user to add a new session when already signed-in. These are problems that are generally independent of AuthZ per-se, but are are currently often used in conjunction with. Single accounts So far, we think that these are solvable problems. |
Why does the browser need to see the scopes? It seems like this is only needs the modifications to how the IdP communicates its requirements back to the browser. The Standardizing the format of these parameters so that the browser understands them doesn't really serve to help. It only puts the browser in a position that might involve their definition; something that is best left to the IdP and RP to arrange between themselves. |
So, in this proposal, we use the
Yeah, that was one of the tensions that occurred to us early on. I'm not strongly convinced either way at the moment, but this is a fair area of tension that is worth investigating. Just to be concrete, is the following more or less what you are suggesting? let {token} = await navigator.credentials.get({
identity: {
providers: [{
// We could introduce a parameter that controls whether to mediate the authorization prompt
// or open a pop-up window to delegate the authorization
authz: true,
// clientId is used by the client_metatadata_endpoint
clientId: "1234",
// loginHint is used by the browser to match up accounts
loginHint: "[email protected]",
// configURL is used to fetch the endpoints
configURL: "https://idp.example/",
// nonce is NOT used by the browser, and is an opaque token passed to the id_assertion_endpoint
// maybe it should be moved to "params" instead?
nonce: "234234",
// A grab bag of parameters that get passed from the
// RP to the IdP but that don't really play any role with
// the browser.
params: {
// scope is opaque to the browser
"scope": [
"drive.readonly",
"calendar.readonly",
],
"responseType": ["id_token"],
"IDP_SPECIFIC_PARAM": "1",
"foo": "BAR",
"ETC": "MOAR",
}
},
}
// If possible, return without prompting the user, if not possible
// prompt the user.
mediation: "optional",
}); Does this match more or less what you are suggesting? A few things that occurred to me when we ran into this:
There is probably a few more considerations to take. Your intuition matches some of mine, and we ran into this tension early on. Like I said, I'm not convinced either way here, but that's the kinda of question that we'll need to figure out as we go. So far, our intuition has been that the sweet spot in terms of extensibility is to reuse concepts from OpenID (example) and SAML, and provide a grab bag of parameters for anything that the browser doesn't need to know. |
what will the browser do, intercepting the release of information from the IdP when that information is encrypted for the SP (RP) to consume? also, signed releases from the IdP or signed requests from the SP - will these be passed through intact? |
Something I am confused on, this proposal implies that yes the browser does want to get out of the way for providing ID, that part is still true, but the extra sharing enabled by this that would normally be shared by 3rd party cookies seems like it’s ripe for abuse because of the unlimited access implied. Is this off base? How does a user understand the difference between sharing their identity and sharing something as broad as ongoing medical history or social graph? |
So unweird it and move it to "params" too. Can you give me a good reason that this is something that the browser needs to care about? I can't see any.
Sure, but the question is whether browsers need to mediate. We need to be sure that the cost of intermediation (which is not just the cost of specifying and implementing stuff, it's the externalities generated that affect RPs and IdPs) is justified.
It depends on how you see the interaction pattern here. From my perspective, I consider the browser's responsibility being limited to presenting a choice to the user in terms they understand and act on, then getting out of the way. Being able to present account details (pictures, names, etc...) is part of that, but scopes in general can't be subject to that sort of comprehension. Sure, some scopes are comprehensible, but those aren't the interesting ones. My sense is that if we're inferring what needs to happen based on the parameter set, we've already given RPs this control. We just haven't acknowledged it.
Nothing. That's an exchange that occurs well past the point that (the/any) privacy loss has occurred. More below.
Concretely, the privacy loss occurs at the point at which a user decides to allow an RP to talk to an IdP. Beyond that, my interest in browser participation is limited, except to the extent that it can make IdP/SP/RP tasks easier. Yes, there is the risk that there is a gap between what the IdP decides to share and what the user expects them to share. That's a constant in all of this. But from the perspective of a browser, we lost the ability to police that interaction at the point we allow any information to pass between RP and IdP. So, rather than pretend that we have control and attempt to rein things in, we should instead embrace that and help RPs and IdPs get their jobs done. One of the things I've noticed about these APIs is that they tend to grow all sorts of capabilities. The identity space is complex and so the systems that support them naturally tend to become similarly complex. What I don't want to have happen (as this case demonstrates very nicely) is the browser API grows to match. It doesn't need to. Most of the complexity growth can be left to RPs and IdPs to manage. That is, "params" can grow in complexity, without our permission or involvement. |
Reiterating my comment from the CG meeting yesterday: This growth of the browser API, the unsolved challenge of a not-yet-logged-in user, and the introduction of the navigation here indicate to me that it may be time to re-visit the proposal I made before my parental leave as a "browser-out-of-the-way" version of FedCM. |
I'm curious how the popup will be treated in more detail:
Basically, I'd like for the browser to be able to isolate the popup from the opener entirely if it so desires. |
Titouan -- yes, that's the intention, IdP.resolve should be the only way to communicate. |
Ok thanks! Could you clarify the lack of |
In this CL, we expose - behind a flag - a "mode" parameter in the FedCM API and plumb it to the browser process through the mojom interface. This starts allowing the FedCM API to degrade more gracefully when the user is *not* logged-in to the IdP. Specific feature request here from Mozilla: https://github.com/fedidcg/FedCM/issues/442#issuecomment-1675007152 The "mode" (={button, widget}) parameter controls the internal algorithms of FedCM to handle logged-out users. In mode=button, the browser opens a pop-up window when users are logged-out, whereas in mode=widget (the default) the prompt is just automatically dismissed. Part (pre-requisite) for this larger feature: w3c-fedid/FedCM#477 (comment) Privacy and Security Early Reviews: https://docs.google.com/document/d/1WtiG-JwlcuCJPI0EZdo61vxg6sjTAuxFFm5I2Jk4x6s/edit The browser implementation, that will actually change based on this flag, is being implemented in a separate CL (see gerrit CL dependency). Bug: 1429083 Change-Id: If9f6b453cc5e16c479b89a16191d010892bd0377 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4779439 Commit-Queue: Sam Goto <[email protected]> Reviewed-by: Yi Gu <[email protected]> Reviewed-by: Emily Stark <[email protected]> Cr-Commit-Position: refs/heads/main@{#1204181}
Hi, I am one of the developers currently evaluating implementation of FedCM in Keycloak (keycloak/keycloak#16834). And the issues outlined here are most certainly a blocker for us to have FedCM be a useful component of our stack. Keycloak also needs to support more advanced OAuth features such as Rich Authorization Requests, Proof Key for Code Exchange (PKCE) and Demonstrating Proof-of-Possession (DPoP), which we don't really see a way forward for supporting these features under FedCM as things are now. It seems to us that with FedCM all these protocols will have to be sort of re-invented, rather than serving as a foundation for existing protocols such as OAuth. We think the account account selection and in general handling things more in a browser-native manner are all great ideas, and those should serve as a gateway, but from there on there are established protocols that have been decades in the making should be able to take over. I'd highly recommend to start some discussions in the OAuth and OIDC working groups, as they are aware of the problems with tracking and 3rd-party cookies, to which there is currently no workable solution. FedCM could be that solution, but I feel strongly it should serve as a facilitator, and not a whole new protocol to deal with that has to cherry-pick things from existing standards over time. Especially when combining this with the aggressive time-line for the phase-out of cookies and the time it will take for applications to start switching to this new model, as well as the fact that Google Chrome is currently the only one supporting this, it's a recipe for disaster in terms of adoption. |
@jonkoops I completely agree. While thinking in the context of #58, I realized that maybe a much simpler solution can exist for dealing with 3rd-party cookies issue than whole new protocol, |
@jonkoops these conversations have been on and off for the past year or two (many folks from OpenID have been part of the W3C FedCM effort) but we plan to ramp this up more actively starting this week at OSW and IETF 120. |
Great! Do let us know if there are any developments in this area we can act on, because we're very open to anything that could help us resolve issues around 3rd-party cookies. |
The proposal earlier in this issue is combining several items that are orthogonal to each other. Some of them have been split out into other issues already (e.g. button flow). I have split the remaining parts of the proposal into two issues: I think that will help with focusing the conversation, especially since some things seem more controversial than others. I am leaving this issue open to talk more generally about use cases that are not addressed with the proposals (e.g. #477 (comment)) |
I'm not super familiar with all of these features, but wouldn't some (all?) of them be possible to be implemented with this existing proposal? At least "Rich Authorization Requests" seems to be possible to me, but skimming through the other ones, that seems like possible too? https://github.com/fedidcg/FedCM/issues/556
If so, this is currently implemented behind a flag in Chrome, so it would be great if you could give that a try. If not, can you expand which of these features wouldn't be able to be represented by |
Have someone evaluated using OpenID Connect for Verifiable Presentations? I only noticed #49 and https://github.com/fedidcg/FedCM/issues/240#issuecomment-1100253585 |
Yes, but in the context of this API instead. Is that what you are looking for? |
Thank you, @samuelgoto. I'm considering using OIDC4VP in the context discussed here, which involves authorizing access to arbitrary kinds of data. First of all, I would like to understand what type of token is expected to be returned by the IdP. TBH, I don't know if the role should still be called IdP once its responsibility is a more general authorization service. I'm hesitant to have it be an access token. Instead, I would rather see some credential, possibly VP, which later could be exchanged for an access token using OAuth 2.0 Token Exchange or User-Managed Access (UMA) 2.0 Grant for OAuth 2.0 Authorization claim pushing. The use cases I'm thinking about also include access delegation. For example, ACME Inc. shares a calendar with Alice, and later, Alice wants to grant RP access to her calendars and ACME's calendar she has access to. Of course, the "IdP" (user's authorization service) would deal with related complexity and issuing relevant credentials. But in that scenario, RS with Alice's calendars and RS with ACME's calendars would be in different security domains, and using the access tokens directly wouldn't be appropriate. |
For all of you who had comments on the "scope" part of this proposal, please see an updated proposal for that aspect in w3c-fedid/custom-requests#4 . |
The original problem statement in this issue included:
w3c-fedid/custom-requests#4 seems to focus on limited extensibility, which still focuses on information that could be considered a part of the user profile. Is it expected that w3c-fedid/custom-requests#1 will address that unbounded number of APIs? There is also something that I would see as a special case related to the social graph; the original problem statement mentions it in:
Right away, I want to reference Contact Picker API, which hopefully is aligned with the general information expected in a user profile. The social graph seems like a special case since it is not something that I would consider a direct part of a user profile. Instead, it seems like a collection of user profiles, so at least the data model should be consistent. |
Yes. |
FYI this feature is in origin trial starting from Chrome 126: https://developers.google.com/privacy-sandbox/blog/fedcm-chrome-126-updates |
I'm going to resolve this issue, since we broke this into many smaller and more specific ones that are more tractable when looked at in isolation. Here they go:
Feel free to reopen if you feel like something that was discussed here isn't represented in these smaller / more granular issues, and I'll make sure I'll add them to the list. |
Identity Federation allows users to login to websites using their accounts from identity providers.
For consumers [1], for the most part (we estimate ~80% of the traffic), sharing your basic account profile covers the majority of the use cases, because it provides relying parties most of what they need to create an account: a signed and stable unique identifier and the user’s name, profile picture and email address.
However, occasionally, websites need more information about the user to operate: e.g. their schedule, their friends list, their amazon prime status, etc. The need for the different aspects of the user’s data varies greatly across websites and identity providers (e.g. some identity providers know their user’s email addresses, some don’t), so Identity Providers offer them an enumeration that they can pick and choose from to give websites access to HTTP APIs. The enumeration, encapsulation and addressing of APIs are known as “OAuth scopes” (e.g. we use, informally, “The website is asking access for the user’s Google Drive’s OAuth Scope” ).
For example, on rp.com, after choosing an account, the website asks specifically for the user’s “date of birth”, in addition to the user’s basic profile. The Identity Provider is happy to provide to the relying party as long as it gets confirmation from their users that that’s strictly necessary for the task that they are trying to accomplish (create an account on the RP, which customizes their user experience depending on their user’s age).
The authorization flows are currently built using low level primitives, like top-level redirects / pop-up windows / iframes / cookies (as you can see above), which are hard to distinguish from the same practices that have been abused for tracking. For example, at the end of authorization flows, the website gets an OAuth “access token”, which can be refreshed/re-issued using iframes and third party cookies. As browser vendors impose constraints in these low level primitives, we expect parts of these flows to break.
The Problem
The problem is that, as browser vendors increasingly work to prevent tracking on the web, they struggle to distinguish federation from tracking, and so unintentionally affect these flows. We call this “The Classification Problem”: it is hard to distinguish federation from tracking.
Thankfully, through FedCM, browser vendors are starting to find ways to solve “The Classification Problem” for at least common parts of the flows: authorizing the access of the user’s basic profile information, which we’ll call Standard Basic Profile Authorization Process for now (Authentication is sometimes used here, but that seems overloaded and inconsistent to us).
This is currently accomplished by mediating two flows: (a) account choosing and (b) authorizing access to the user’s profile:
FedCM managed to do that because (a) there is a lot of uniformity in account choosing between IdPs (e.g. how to represent an account) and (b) there is a list of well-known attributes (OIDC’s standard claims and HTML’s autocomplete ontology) of the user’s identity that can be requested (names, email addresses, profile pictures, etc), so it was plausible to anticipate and build a mediated Authorization flow to exchange the user’s standard profile.
However, beyond account choosing and the standard profile, there is an unbounded number of APIs that an Identity Provider can provide to Relying Parties.
For example, the browser would never be able to know how to ask the user for permission to access “Social Graph” because it doesn’t know what a “Social Graph” is (whereas it can, and indeed does, know the fields of the user’s basic profile: “name, email and profile picture”).
So, what worked to mediate authorization of the user’s standard profile doesn’t seem like it would work for authorization of the user’s identity more generally: IdPs need an extensible mechanism that allows them to express the authorization using their own words.
So, how should the browser allow IdPs to share with the RP more of the user’s data, without lowering the privacy properties of FedCM?
The text was updated successfully, but these errors were encountered: