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

Create a passkey and a federated assertion in a single unified prompt #671

Open
samuelgoto opened this issue Oct 30, 2024 · 12 comments
Open

Comments

@samuelgoto
Copy link
Collaborator

The general idea of separating identification from authentication has been around for a while, specially in the context of Social login, where OIDC is (unfortunately?) used for both. Since we are getting to the point where we can run an oauth profile in browser UI, it also means that the browser can also unify various flows into a more cohesive UI prompt.

I think the general sense is that there are many circumstances where we'd like to use OIDC for identification but very quickly use passkeys for what they are best, authentication. Some of the obvious reasons is that it feels unnecessary to have the IdP involved in every Sign-in.

While this isn't necessarily a new trend, it wasn't clear to me how we'd reconcile these APIs: do we extend the Credential Manager API? do we embed a FedCM in a Passkey create? Do we embed a Passkey create in a FedCM call?

What I think recently changed was the introduction of mediation: conditional to WebAuthn and the de-coupling between the WebAuthn API and the construction of the UI.

So, in this framing, we would:

  1. call the WebAuthn API in conditional mediation mode which returns one promise and
  2. call the FedCM API which returns another promise

The browser, with both of these calls made, can know that both calls are hanging, and can build a UI that can represent both prompts, largely TBD what the pixels actually look like. When the prompt is accepted, both promises are resolved.

Here is a code snippet of what that might look like:

// Creates a "conditional" passkey create
const passkey = navigator.credentials.create({
  publicKey: ...,
  mediation: "conditional"
});

// Knows that there is a "hanging" passkey create and unifies the permission prompt
const identity = navigator.credentials.get({
  identity: ...
});

// Waits for both promises to be resolved
await Promises.all(passkey, identity);
@npm1
Copy link
Collaborator

npm1 commented Oct 30, 2024

That might work. The current behavior says "For create(), if a user has previously consented to credential creation and the user agent knows it recently mediated an authentication, then the create() call may resolve without additional prominent modal interaction. If the user agent did not recently mediate an authentication or does not have consent for credential creation, then the call must throw a "NotAllowedError" DOMException." so technically it currently would just throw right away but perhaps it can wait if there is a pending FedCM call. I'm not sure if it is compatible with the existing semantics but if not then another mediation type can be added too...

@timcappalli
Copy link

This would be allowed in the existing WebAuthn Conditional Create mode.

One thing to think about would be an input to FedCM that defines a mapping from the FedCM response to user.name.

The other thing we'd have to think about is how to get user.id as that is a server-side account identifier.

@cbiesinger
Copy link
Collaborator

The accounts endpoint does respond with an id and a name already:
https://w3c-fedid.github.io/FedCM/#dictdef-identityprovideraccount

@timcappalli
Copy link

The accounts endpoint does respond with an id and a name already:
https://w3c-fedid.github.io/FedCM/#dictdef-identityprovideraccount

Yes, but that's an identifier from the IdP, not the account identifier at the RP.

@philsmart
Copy link
Contributor

Hi Sam,

This is interesting. I have a few questions or observations if you don't mind:

  • My understanding is that both OpenID Connect and SAML require the end-user to authenticate. I’m not sure how you could separate authentication from identification. If you can not, a possible flow of the proposed solution would then be: you first authenticate (in some way) with the authorization server (or Identity Provider, IdP), and then you would have to register a passkey and authenticate again with the service provider (Relying Party, RP)?
    • Granted this may be less troubling for the user when performing a Social Login, as those IdP sessions seem very long-lived. So you log in to your social provider, 'at some point', and then use that account without requiring a further login to seed a passkey login to the RP.
  • If the IdP manages your authentication sessions and uses Single Sign-On (SSO), the user may not necessarily notice or care if the IdP is involved in every sign-in.
  • An IdP may opt to support passkey authentication itself, meaning you might be prompted to use a passkey for the IdP (acting as a WebAuthn RP) and then another for the actual RP. Which is possibly a confusing use of federated authentication.
  • It looks, correctly, like this proposal would not couple passkey authentication to federated authentication? The IdP evaluates and determines how authentication should occur based on the authentication request from the RP. You may not always require passkey authentication; another form of multi-factor authentication (MFA) may be more appropriate for a specific authentication request.
  • The IdP might prefer to be involved in every sign-in to control access. For example, if an employee leaves an organization, they will no longer be able to authenticate to the IdP to access a certain resource (RP). However, some control may be lost if you switch to passkey authentication, though you could periodically request a new federated sign-in to maintain some level of control.

@timcappalli
Copy link

@philsmart the goal with this flow in consumer scenarios would be to use federated sign in for account creation / sign up (e.g. maybe get a verified email and phone number) and then use a passkey for subsequent sign ins directly to the service (federation would not be used for account sign in at the service).

@timcappalli
Copy link

also, everything in this flow is already possible today, it just requires more calls, clicks, and user interactions. The goal is to optimize this scenario by taking advantage of a new WebAuthn capability for passkey creation.

@philsmart
Copy link
Contributor

I see, thanks, @timcappalli. This proposal would be one operating mode of FedCM, but not the only one?

@npm1
Copy link
Collaborator

npm1 commented Oct 31, 2024

My understanding is that FedCM would be invoked as usual, but if the site also tries to create a passkey with conditional mediation (with its separate method) then we'd do that right after FedCM is used. So it's just something optional: if the developer does not want to use passkeys after FedCM, it won't happen.

@philsmart
Copy link
Contributor

I guess this is one step closer to using a Digital Credential (e.g. a verified email address credential) to create a local account and then a passkey to authenticate.

@timcappalli
Copy link

My understanding is that FedCM would be invoked as usual, but if the site also tries to create a passkey with conditional mediation (with its separate method) then we'd do that right after FedCM is used. So it's just something optional: if the developer does not want to use passkeys after FedCM, it won't happen.

exactly

@samuelgoto
Copy link
Collaborator Author

samuelgoto commented Nov 1, 2024

I chatted a bit more with @timcappalli and @balfanz on this at IIW, and learned that this is not actually how the conditional create works. Apparently, the conditional create works by being called after password creation happens, and being "auto accepted" when that happens.

So, I think the correct construction would be:

// Gets the user's identity
const identificaiton = await navigator.credentials.get({
  identity: ...
});

// Create an account on the server
const {id, email} = await fetch(`/create_account.php`, identification);

// Gets a free passkey.
// The following call gets resolved without any permission prompt
// if called within a certain time range from the FedCM one.
const authentication = await navigator.credentials.create({
  publicKey: {
    // ... other stuff
    user: {
       id: id,
       email: email,
    },
    // ... other stuff
  },
  mediation: "conditional"
});

// Sends passkey to the server

@timcappalli did I get this right?

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

No branches or pull requests

5 participants