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

Consider supporting federated login without IDP APIs #3

Closed
bvandersloot-mozilla opened this issue Dec 7, 2022 · 10 comments
Closed

Comments

@bvandersloot-mozilla
Copy link

bvandersloot-mozilla commented Dec 7, 2022

Consider supporting federated login without IDP APIs

This is meant to be a conversation-starter around how we can preserve federated login scenarios without adding IDP APIs.
I don't think this needs to be independent of FedCM. However, because it is a large addition and FedCM is sufficiently complicated, I feel more comfortable first writing it as an independent proposal that could be integrated into FedCM.

The rest of this Issue is a proposal for an integration to Credential Management that enables federated logins to work without third-party cookies. The strategy is to pave the cow-path of existing sign-in mechanisms that use a third-party origin to authenticate, requiring minimal changes by the IDP. The goal is to create an easy on-ramp for IDPs to use the Credential Management API and authenticate users to RPs in browsers that have deprecated third-party cookies.

It is named "federated3" because the "Credential Management API" spec defines a "federated" credential and FedCM used "federated2" for a time.

What is the Expected User Experience?

Registration

Registration with the IDP is unchanged.

Linking Registration

  1. When a user goes to register for an account on a example.com, they click a "Register with IDP" button.
  2. A Browser Permission UI appears asking if the user wants to register on example.com using their information from idp.com.
  3. The user clicks yes and a popup (or new tab) appears of idp.com that allows the user confirm the permissions for example.com.
  4. The popup (or new tab) closes and the user lands back on example.com, logged in.

Alternatively, via new web UI patterns:

  1. When a user goes to register for an account on a example.com, they click a "Register" button.
  2. A "Credential Chooser" appears presenting WebAuthN, passwords, and/or a few IDPs, depending on example.com's choices and the browser's support.
  3. The user selects one of the IDPs.
  4. Proceed from linking Step 3.

RP Log in

  1. When a user goes to log in to example.com, they click a "Log in with IDP" button.
  2. If the user has already linked registered and the credential from last time has not expired, they are immediately logged in.
  3. Otherwise, they proceed from linking registration Step 2.

Alternatively, via new web UI patterns:

  1. When a user goes to log in to example.com, they click a "Log In" button.
  2. If the user has already linked registered any Credentials with this site (of any variety) the user may select from them in browser UI.
  3. The user selects a federated3 credential, proceed to Log in Step 2.

RP Log out

  1. When a user wants to log out of example.com, they click the "Log Out" button.
  2. The user is logged out only on example.com.

What is the Expected RP (or IDP Library) Developer Experience?

Linking Registration

  1. Calls to navigator.credentials.create with argument key credential3 can specify [1,n] different supported identity providers.
  2. The RP can also provide a blob of data to each identity provider that they will get sent with a linking registeration request.
  3. The user is shown those identity providers and can pick from them.
  4. Once a choice is made, you know which provider was chosen and get a blob of data in response.
  5. navigator.credentials.store the result from navigator.credentials.create to remember the user as logged in.

Log in

  1. Calls to navigator.credentials.get with argument key credential3 can specify [1,n] different supported identity providers.
  2. If the user already logged in to your service with one of those IDPs, then you will receive a blob of data from the IDP.
  3. If the user is not logged in with one of those IDPs, but has linked registered with one on this site before, then the user is logged in and you will receive a blob of data from the IDP.
  4. If that blob of data is insufficient, you can re-link re-register with the IDP for more permissions.
  5. Otherwise, navigator.credentials.store the result from navigator.credentials.create to remember the user as logged in.

RP Log out

  1. Call navigator.credentials.preventSilentAccess().

What is the Expected IDP Developer Experience?

Log in

Add a call to any authentication page that:

  1. Checks if a Federated Login Status request is pending
  2. If it is not, return
  3. Process the pending request (including a blob provided by the RP)
  4. Call a Federated Login Status response.

Log out

Add a call to any logout callback that:

  1. Calls a Federated Login Status logout.

How this is Accomplished in Specification?

In order to get the above UX and DX, we need to make only the following changes to the web platform.
This proposal has three key parts, each presented in

A new Permission

We add a new permission, say federated-authentication which is keyed on the origins of the RP and IDP.
Its semantics are that it represents permission for an RP to enter login flows, such as popup, with a given IDP.

A defined Federation Login Status API

We need to have a defined subset of the Login Status API. I propose we introduce at least a subset of it here:

dictionary LoginStatusRequest {
  required string relying_party_origin;
  string request_blob;
}

[Exposed=Window, SecureContext]
interface LoginStatus {
  Promise<LoginStatusRequest?> pendingFederatedLoginRequest();
  void completeFederatedLoginRequest(string? response_blob);
}

This can integrate with any other Login Status API implementation to allow calls to completeFederatedLoginRequest to set logged in status for the RP and IDP.

A new Credential

Finally, we need a new credential type that has the blob from the IDP's call to completeFederatedLoginRequest and options to request it.

[Exposed=Window,
 SecureContext]
interface FederatedCredential3 : Credential {
  readonly attribute USVString blob;
};

dictionary Federated3CredentialRequestOptions {
  sequence<FederatedProviderConfig> providers;
};

dictionary FederatedProviderConfig {
  required USVString loginURL;
  required USVString clientId;
  required USVString blob;
  FederatedProviderDisplayParameters style;
};

enum FederatedProviderDisplayStyles {
  "popup",
  "newtab"
};

enum FederatedProviderDisplayPosition {
  long width;
  long height;
  long left;
  long top;
}

dictionary FederatedProviderDisplayParameters {
  FederatedProviderDisplayStyles? style;
  FederatedProviderDisplayPosition? popupPosition;
};

partial dictionary CredentialRequestOptions {
  Federated3CredentialRequestOptions federated3;
};

partial dictionary CredentialCreationOptions {
  Federated3CredentialRequestOptions federated3;
};

Its [[discovery]] would be "remote" and [[type]] would be "federated3".

This credential would define its [[CollectFromCredentialStore]] and [[Store]] methods to allow storing and retrieving of credentials within the same origin.

This credential would define its [[Create]] method to perform linking registration. This would be something akin to:

  1. Perform Permission Policy check.
  2. Show the user UI to pick a given IDP.
  3. On selection, store a "Pending request" in the browser containing a LoginStatusRequest with the blob the RP provided and the RP's origin.
  4. Navigate to the loginURL chosen, optionally matching the RP requested style.
  5. Await a call to completeFederatedLoginRequest in the IDP page.
  6. Set the federated-authentication permission for (RP, IDP).
  7. Return a credential with the blob argument of completeFederatedLoginRequest.

This credential would define its [[DiscoverFromExternalSource]] method to be what happens when navigator.credentials.get is called, but the user is not currently logged in.

  1. Perform Permission Policy check.
  2. If the user has a Federated3Credential, and has federated-authentication permission for (RP, IDP)
    1. Return that credential.
  3. Otherwise, if the user has a Federated3Credential, present that in a prompt with "pick another account"
    1. If the user selects the existing credential, return it.
  4. Otherwise, return the result of [[Create]] with the same arguments.

Open questions

I think the answer to all of these is yes, with some work.

  1. Can we manage to do front-channel logout if the RP constrains its use of Credentials.
  2. Can this reasonably integrate with other credentials?
  3. Can we support redirect login flows with minor changes to this definition and no change in UX or DX?

Out of scope

  1. Server-side APIs
  2. One-tap-like integrations.
@timcappalli
Copy link
Member

One thing we need to be sure everyone is aligned on is terminology. In the identity world, registration is typically another term for account creation. I think in some of these browser flows, registration could mean either account creation, or "linking" the IdP + account to the browser.

The rest of my comments are in the context of the identity use of registration (account creation).

In many cases, the registration (account creation flow) does not happen in the same browser or on the same device, or may not even be done by the end user (ex: work/school account created on your behalf). So for "RP Log in" step2, what happens if the user already has an account with the IdP, but did not create that account in this browser or device?

RP Log out

Logging out of only the local site is the typical experience for consumer services that use a third party sign in provider, but this is not typical for work/school where global sign out is required. In some of the FedCM discussions, we had talked about potentially having a model where the browser call logout endpoints on behalf of the IdP.

@bvandersloot-mozilla
Copy link
Author

bvandersloot-mozilla commented Jan 3, 2023

One thing we need to be sure everyone is aligned on is terminology. In the identity world, registration is typically another term for account creation. I think in some of these browser flows, registration could mean either account creation, or "linking" the IdP + account to the browser.

Ah, good to know. I've added strikethroughs and replacement text above. I was using registration as I understood it was used in FedCM- to mean linking.

In many cases, the registration (account creation flow) does not happen in the same browser or on the same device, or may not even be done by the end user (ex: work/school account created on your behalf). So for "RP Log in" step2, what happens if the user already has an account with the IdP, but did not create that account in this browser or device?

They go to Linking step2 and in step3 the IDP "popup (or new tab) appears of idp.com that allows the user confirm the permissions for example.com" would allow them to log in to the IDP before making permissions decisions.

Logging out of only the local site is the typical experience for consumer services that use a third party sign in provider, but this is not typical for work/school where global sign out is required. In some of the FedCM discussions, we had talked about potentially having a model where the browser call logout endpoints on behalf of the IdP.

Some version of this is probably still possible in depending on the structure of the endpoints. We would need to add them to some in-browser store at linking-time and add an IDP logout to the Federated Login Status API that issues those requests with first-party cookies. Does that sound reasonable as a high-level description?

@martinthomson
Copy link

I provided a version of this feedback in a discussion with @bvandersloot-mozilla, but I thought that I'd surface my feedback on it.

An extension to the FedCM is my preferred way to approach this; though the fully-worked standalone example is helpful for understanding the ideas without encumbrance, we have an obligation to reconcile our changes with what exists. There are also some key aspects of FedCM that are worth retaining.

There are 2 things here that are fairly critical extensions to the basic functions that FedCM provides:

  1. A way for a user to log in or sign up to an IdP.
  2. A way for the user and IdP to interact and define more precisely what the identity (or token) means.

Both address what I consider are major shortcomings of the current FedCM approach. They both largely follow the same high-level interaction, but the latter really opens up a bunch of authorization cases.

I like @timcappalli's insistence on the word "linking"; it (coincidentally?) highlights my main concern with this sort of interaction model. No matter what happens here, this sort of API enables linkability. That is, it allows the RP and IdP to link activity on their respective sites to the same person. Linkability enables cross-site tracking, which is undesirable in the general case and why we now insist on sites gaining explicit and deliberate permission prior to allowing it.

That brings me to my next point. If we are to open up the API to authorization use cases (which I think is justified), there are constraints that a browser might want to apply that aren't necessarily present in existing systems.

I'm going to use a bit of a bad example here to illustrate my point, but it is the best I have that illustrates the potential for there to be a gap between actuality and practice, so bear with me. Let's say that you have a vendor of adult beverages who wants some certainty that their customer is over 21. Now, leaving aside the many problems with this use case1, you might imagine the vendor being an RP to an IdP that can authorize the generation of a verifiable credential (VC) that says "IdP X has verified that this person is over 21".

There is a gap here between the user's understanding of that interaction and the reality. A user might expect that there is a single bit of information flowing from IdP to RP: whether they are over 21 or not. Leaving aside the fact that one bit of information can be enough to enable tracking, the sort of exchange contemplated here contains far more information than that. Consequently, whether or not the RP and IdP act to link user identities across sites, they now have that capability.

Part of building a system like this is building in controls (like choosers and permissions prompts), but we also need to think about accountability for those cases where sites gain access to these capabilities. These controls need to be commensurate with the capabilities that are being extended to sites.

Thankfully, I think that the existing FedCM approach provides all we need here to hang off. The account details that the IdP extends to the RP are bound to an identifier that the browser understands and can present to the user. That identifier (perhaps with a friendly name and image provided by the IdP) gives us a strong handle that can be the basis of providing feedback and control over the linkage.

Insisting on authorization flows being elevated to employ all the characteristics of federated login might seem like overreach. However, the only requirement is the inclusion of a label for each authorization: the identifier. For each authorization, the IdP can maintain a set of tuples that include the RP, the allocated identifier, the set of resources that were exchanged/authorized with the RP under that identifier, plus supplementary information (a name, a picture, access times, etc...). The browser can maintain its own view, with stores for each RP that record the IdP, the identifier as known to the RP, the identifier as known to the IdP, any active tokens, and other information.

In the degenerate case, the allocated identifier might be the primary identifier for the user at the IdP (user@idp.example), but this offers a great opportunity to engage with projected identity (<random-seeming>@auth.idp.example). IdPs can allocate identifiers that aren't linkable to the canonical identifier. This identifier is then a handle on a specific IdP-RP interaction. Projected identity is therefore optional, but it gives IdPs a more privacy-respecting option.

There's a lot more to this that needs to be worked out, like the various revocation flows, but it would help if we could get alignment on a basic model like the one suggested here. Most of these details should become more tractable once there is a good set of handles that all actors can agree to.

Footnotes

  1. It's hard to know exactly where to start on the problems with this example, so I'll just point out that online age verification has a long and chequered history. I will, however, point out that Privacy Pass and Private State Tokens (warning: hard hat area) provide an alternative approach for passing low entropy signals between sites and so might offer a superior approach when applied to this class of problem.

@johannhof
Copy link
Member

Hey @bvandersloot-mozilla, this is an interesting proposal! I had a few questions which I'll try to ask at the call later today, but wanted to also post them here to allow for preparation or following up:

  1. Can you point to any documented breakage or developer experience issues that this would help with? I'm not saying that they don't exist, it would just help inform the discussion to know what we're trying to solve for. So, in other words, what is the API gap that we're filling with this vs. existing FedCM?

"Can we manage to do front-channel logout if the RP constrains its use of Credentials."

can you expand on what "constrains" means here?

  1. How does your proposal interact with @martinthomson's comment above? (I had a quick chat with Martin to clarify some aspects of what he's saying here, which I think is approximately that we're still exchanging identifiers even for authentication and should embrace it)? I think it would be useful to describe how the "authentication" use case influences the design decisions in your proposal.

  2. Related to that:

"Can we support redirect login flows with minor changes to this definition and no change in UX or DX?"

Can you expand on this question?

@timcappalli
Copy link
Member

timcappalli commented Jan 23, 2023

A way for a user to log in or sign up to an IdP.

In most cases, the user is not signing up for an account at the IdP.

We should explicitly split out accounts, relationships, and credentials:

  • Local account, local credential
  • Local account, federated credential

So we end up with the following inline combinations:

  1. User creates a local account at the RP, using a local credential(s) [common]
  2. User signs in with a local account at the RP [common]
  3. User creates a local account, using an existing federated identity (common)
  4. User creates a local account, using an existing federated identity, that uses an existing federated identity (aka chaining) [common in work/school]
  5. User creates a local account, using a newly created federated identity [not very common for 3P consumer]
  6. User adds a federated identity to an existing local account [fairly common, happens out of band]
@bvandersloot-mozilla
Copy link
Author

1. Can you point to any documented breakage or developer experience issues that this would help with? I'm not saying that they don't exist, it would just help inform the discussion to know what we're trying to solve for. So, in other words, what is the API gap that we're filling with this vs. existing FedCM?

It is hard to point to exactly one issue, but there are two things this helps address:

  1. Not-logged-in users (#67, #283, #380)
  2. IDPs that do not want to implement the new API endpoints (can't find an issue, but has been discussed in meetings)

"Can we manage to do front-channel logout if the RP constrains its use of Credentials."

can you expand on what "constrains" means here?

Only use the token via the Credential, rather than storing it in Local Storage and not using the API. This allows us to delete the relevant Credentials to prevent their use, effectively logging the user out.

3. How does your proposal interact with @martinthomson's comment above? (I had a quick chat with Martin to clarify some aspects of what he's saying here, which I think is approximately that we're still exchanging identifiers even for authentication and should embrace it)? I think it would be useful to describe how the "authentication" use case influences the design decisions in your proposal.

RE @martinthomson's comment:

  1. This credential type would be integrated with "identity" using its CredentialRequestOptions.
  2. The primary point of this design that is influenced by the AuthN use case is the FederatedProviderConfig which is pretty bare to just transmit a blob of data associated with a clientId and whatever information is needed to make the flow happen (loginURL, style).
  3. The best way to combine Martin's view with this proposal is to roll some of the IdentityProviderAccount information into this proposal's completeFederatedLoginRequest. In particular, a user-friendly account name and picture URL. This would give the user some way to know what account this is when managing it. Probably unifying the two flows (Server API and Federated Login Status API) would be a tactic I prefer, via changing the argument of completeFederatedLoginRequest to objects representing the Server API responses.
  4. Additionally, we would need to augment the IdentityProviderToken to represent a broader set of claims representable to the user. The example Martin used here was age > 21. This gives the user the chance to understand what is being exchanged a bit better

@martinthomson: feel free to correct me if I misunderstand you here.

4. Related to that:

"Can we support redirect login flows with minor changes to this definition and no change in UX or DX?"

Can you expand on this question?

Currently this proposal assumes a new tab/window is opened to log in to the IDP. This question is asking if we can manage to do this in the same tab with this approximate structure.

This is a little last minute, but I'll try to get my thoughts here before our meeting. Sorry for any places where I'm not verbose enough.

@martinthomson
Copy link

Additionally, we would need to augment the IdentityProviderToken to represent a broader set of claims representable to the user. The example Martin used here was age > 21. This gives the user the chance to understand what is being exchanged a bit better

I think that the >21 example was probably misconstrued. That was a negative example, as in I wanted to highlight something that browsers shouldn't do. I deliberately chose a bad example for that reason, but I guess it's hard to get this stuff right...

My core point was that browsers shouldn't be in the business of arbitrating at that level of granularity. The shape of the API is such that RP and IdP join identity; that's the real effect of any token exchange, no matter what limitations might be claimed.

Once identity is joined, the sites can exchange whatever information they choose for this user, bound only by what they know about the user. Of course, trustworthy sites will give users control over what they exchange. But at a technical level, the browser is no longer involved.

What I am aiming to do here is ensure that the browser has a handle (an identifier) that it can use as a handle for talking to the user about what is going on. That identifier can be used by both sites so that all parties have a basis for building a shared understanding:

  • The browser can say that X is provided by the IdP and given to the RP.
  • The IdP can use X when talking about the set of information that is being exchanged with the RP.
  • The RP can use X when talking about what it has obtained from the IdP.

The information we're talking about here could be traits or authorization information. The browser doesn't need to care. But it's not an opaque token, it's something that is both user-visible and a key part of all subsequent interactions. (Adding a name and picture is potentially helpful in making the handle more manageable and accessible, but secondary.)

Of more serious import is whether we can do anything to ensure that the identifier can be meaningful to users. I'm not sure that we have a solid defense here. What stops an IdP from minting identifiers that appear to reveal nothing (over21@example.com for instance), but then using the API to covertly exchange far more than that?

@samuelgoto
Copy link

samuelgoto commented Jan 24, 2023

@bvandersloot-mozilla just wanted to write down here what I hopefully relaid in person at the FedID CG call.

First and foremost, I wanted to say that we all got this proposal with a good amount of excitement: it was great to see Firefox being creative about variations here and propose ways in which we could help the ecosystem!

Second, I wanted to give some validation about the problem statement: preserve federated login scenarios without adding IDP APIs. Decreasing operational and deployment costs for IdPs is a really important problem that we would all agree is unsatisfying about FedCM at the moment.

Third, I'm sure this was intended by you, but something to be said explicitly is that FedCM lacks an "extensibility" story: ways in which IdPs can innovate without asking for permission. That, in addition to decreasing costs, is an area that we haven't investigated enough, and are interested in seeing proposals!

Fourth, I loved the way you went about it: you isolated your proposal into something self-contained and easy to review, but made it clear that it probably should live as an extension of something else rather than something on its own. I think that's a great way to make forward progress.

Fifth, I wanted to say that, while it is great to see exploration, we are / were struggling to understand the proposal (specially because it seems that @martinthomson and you are talking about different things when we first read).

So, here are some (clarifying-and-genuinely-non-judgemental) questions that I asked you today, along with what I think we heard from you:

  1. Is the permission here per IdP or per Rp/IdP pair? Ben: the latter, per Rp/IdP pair.
  2. What does the permission entitle? Third party cookies? Ben: nope, IdP gets loaded as a top-level frame, first party context.
  3. At what point does the IdP learn about the Rp? Ben: after the permission is accepted, the browser loads the IdP in a top-level frame with the information about the requestor, the Rp.

And three questions that I think I don't think I got a clear answer from you:

  1. Isn't this already possible with link decoration, postMessage() and window.open() without any browser permissions? Why would an IdP choose to use this (more constrained and less performant) API if it can already do this? Ben: Good question, let me get back to you on this.
  2. You made a few references to the Login Status API but this feels similar to the Storage Access API when it comes to linking identities with a permission prompt. What's the relationship between this and SAA?
  3. Does this API unlock something special later? For example, can the IdP do front-channel logout?

When we started FedCM (TPAC2020) we looked at three different variations. We called them: the Permissions-oriented variation, the Mediation-oriented (what we ended up with) variation and the Delegation-oriented (Personas-like) variation.

We were very deliberate about starting with mediation, but we always thought of the permission variation as complementary and co-existing, rather than in opposition of: mediation is always going to be behind, paving the cowpath, but we need an API that allows IdP to innovate without asking for (the browsers) permission.

I think this proposal has a lot of similarities to the Permissions-oriented variation: a permission prompt that allows IdPs to control the rest of the flow. Here is a mock that we used early on [1].

Does [1] seem right? Is this more or less what you had in mind from a UX / permission perspective (modulo strings, obviously), but also from a browser affordance perspective (i.e. a pop-up window, with an origin attribution, but otherwise capable of loading the expressivity power of HTML/JS/CSS).

If this is more or less what you had in mind, I think you'd find a lot of synergy here from us.

[1]

Untitled

@johannhof
Copy link
Member

As I already expressed in the meeting, I agree with @martinthomson's point here:

My core point was that browsers shouldn't be in the business of arbitrating at that level of granularity. The shape of the API is such that RP and IdP join identity; that's the real effect of any token exchange, no matter what limitations might be claimed.

Thinking about any kind of other semantic than sharing identity (even with the restrictions Martin describes) seems to be an interesting challenge, but from my understanding of this thread and #3 (comment) it doesn't seem to be the primary goal/pain point that this proposal is solving. As such, I would suggest that it poses a major distraction and that we should separate out the two ideas :)

As mentioned before, I'm otherwise very interested in this proposal and the potential for building a more flexible FedCM(-like) flow for developers, as long as we manage to keep the user experience relevant and contextual.

@bvandersloot-mozilla
Copy link
Author

I think this benefits from being split into several interrelated PRs on FedCM, as suggested by commenters. I've split the core points into fedidcg/FedCM#441, fedidcg/FedCM#442, fedidcg/FedCM#443, and a comment on fedidcg/FedCM#429 that led to PR fedidcg/FedCM#439.

Given those discussions/proposals on FedCM, I will close this as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants