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

Non-modal registration during conditional assertion #1929

Closed
pascoej opened this issue Jul 25, 2023 · 12 comments · Fixed by #1951
Closed

Non-modal registration during conditional assertion #1929

pascoej opened this issue Jul 25, 2023 · 12 comments · Fixed by #1951
Assignees
Milestone

Comments

@pascoej
Copy link
Contributor

pascoej commented Jul 25, 2023

Description

WebAuthn UIs for registration in browsers are typically intrusive. This presents a challenge for sites that are transitioning from usernames and passwords because RPs have to find the right moment to prompt the user to upgrade to a WebAuthn credential.

WebAuthn could provide a way for RPs to signal at time of login that they want to create WebAuthn credential for any users logging in with a username/password. These log in pages can already use conditional assertions to show credentials in the user agent's AutoFill drop down. This existing call could be extended with an extension to optionally register a credential whenever the user opts to AutoFill a username and password.

I've written an explainer linked below about how this might work.

Related Links

Explainer: WebAuthn Conditional Registration Extension

@emlun
Copy link
Member

emlun commented Jul 26, 2023

Duplicate of #1862 ? Though there's new substance here, namely the concrete proposal for how this could work.

See also: #1533, #1568

One comment on the proposal: how does the client know that "an authentication ceremony is [completed] via non-WebAuthn means"? Authentication completion isn't necessarily tied to a form submission event or page navigation, for example.

Perhaps the extension inputs need to include something like an AbortSignal that the caller will use to indicate that the pending Promise should switch over to the registration behaviour?

@agl
Copy link
Contributor

agl commented Jul 26, 2023

Notes, mostly for myself so that I remember things during any discussion today:

The explainer currently envisions putting the creation options inside an extension in a conditional(?) get request. That could either be done by forking the creation options (which would then have to be kept in sync and would be quite a lot of IDL) or else by referencing the existing creation options. But the existing options include their own extensions block, and the same IDL object is used for both creation and assertion, thus that would be a recursive IDL object.

Alternatively one could imagine allowing concurrent conditional get and create requests, although that would require tweaking some other logic to allow multiple concurrent requests.

Next, creating a credential requires specifying things like the user.name, user.displayName, and user.id. But if the site has to set the creation parameters at the time of making a conditional get request, it probably doesn't know who the user is. So how can it set those values? While user.name could be taken from the password-based account that the user used, and not all systems use user.displayName, user.id seems thorny.

Lastly, signing in often navigates the page, which aborts all outstanding conditional requests. It might be a little complex for sites to sequence things so that the registration isn't truncated by the navigation and lost.

This is all making me wonder whether a conditional create shouldn't be allowed for a short time after an autofilled sign-in, as a separate request.

@pascoej
Copy link
Contributor Author

pascoej commented Jul 26, 2023

how does the client know that "an authentication ceremony is [completed] via non-WebAuthn means"?

I don't think the client necessarily needs to know. I think it could be hard to get the heuristics right for those manually entering credentials. However, if a password manager is being used, the user agent can be certain about the time that it autofills. Many password managers also collect some kind of user verification at time of autofill, which could be used for the credential creation as well.

thus that would be a recursive IDL object.

Is this a problem? (though made moot by below)

Next, creating a credential requires specifying things like the user.name, user.displayName, and user.id. But if the site has to set the creation parameters at the time of making a conditional get request, it probably doesn't know who the user is.

This is a good point. Perhaps the creation options could be provided by a callback.

Lastly, signing in often navigates the page, which aborts all outstanding conditional requests. It might be a little complex for sites to sequence things so that the registration isn't truncated by the navigation and lost.

Typically the user agent does the actual navigation / submits the form after an autofill, so I don't think this would be an issue.

@MasterKale
Copy link
Contributor

Lastly, signing in often navigates the page, which aborts all outstanding conditional requests. It might be a little complex for sites to sequence things so that the registration isn't truncated by the navigation and lost.

Typically the user agent does the actual navigation / submits the form after an autofill, so I don't think this would be an issue.

Something to consider here is that single-page applications won't perform any actual browser navigation after a WebAuthn ceremony, which has already become a pain point with Safari because the "freebie" indirect user gesture gets consumed and isn't refreshed unless the page is hard-reloaded (and JS frameworks make it easier to write ways to unintuitively indirectly invoke WebAuthn.) I wonder how that would factor into this new feature.

@MasterKale
Copy link
Contributor

Also do we envision this being for new user onboarding, or a new capability to assist RP's with existing user accounts to silently upgrade existing users to using passkeys?

@MasterKale
Copy link
Contributor

@pascoej Based on the title of this Issue I was anticipating seeing "mediation": "conditional" in the example navigator.credentials.get() call in the Explainer, but the option is missing. Is that a typo, or is the door being left open for this to proposal to include modal use of WebAuthn as well?

@pascoej
Copy link
Contributor Author

pascoej commented Jul 26, 2023

@pascoej Based on the title of this Issue I was anticipating seeing "mediation": "conditional" in the example navigator.credentials.get() call in the Explainer, but the option is missing. Is that a typo, or is the door being left open for this to proposal to include modal use of WebAuthn as well?

Yes, that's a typo. I fixed it. Thank you.

@MasterKale
Copy link
Contributor

I finally had a chance to give the Explainer a good look. Looking at this first example in the Explainer...

const assertionOrRegistration = await navigator.credentials.get({
  mediation: 'conditional'
  publicKey: {
    challenge: ...,
    extensions: {
      conditionalRegistration: () => {
        challenge: ...,
        user: ...,
        rp: ...,
        authenticatorSelection: {
          residentKey: "preferred",
        },
      }
    },
  }
});

Perhaps the creation options could be provided by a callback.

Is this intended to be the single conditional mediation request initiated on page load to also support use of browser autofill for users with passkeys? If so, I can't see how the shape of this first proposed API could support both auth and registration, because when would conditionalRegistration get executed? It has to happen after username and password submission, but guidance on the bootstraping of conditional mediation has been to make the conditonal .get() request on page load, before the user ever sees UI to enter a username and password. It's unclear to me right now how the initial authentication could happen for the RP to be able to then specify a valid user ID and username in the return value for conditionalRegistration.

Because the website had an ongoing conditional mediation assertion with this extension set, the user agent choses to automatically create a passkey for the account.

How does this technique hold up e.g. hard page reloads for server-rendered sites? I can see how a Single-Page App would be able to maintain context of a conditional mediation request being in-flight if username + password are submitted as a fetch request. But if a typical server-rendered site does a typical <form> submission on username + password submission then that context is lost. It sounds like a lot of technical complexity for browsers to implement something to keep track of the fact that "conditional mediation w/conditional registration" was launched on username-entry.html, but then the user is redirected to success.html after submitting a valid username + password. Would the RP need to re-call .get() on success.html for the new passkey to get silently created?

Potential alternative (Separate create call)

The more I think about all of this, the more I think I like the idea of the split APIs. Whether SPA or server-rendered, both can call conditional mediation in the usual manner, plus the new extension:

const registration = await navigator.credentials.get({
  mediation: 'conditional'
  publicKey: {
    ...
    extensions: {
      conditionalCreate: true,
    },
  },
});

Then, whether the SPA keeps the page context or the page does a hard reload, a conditional .create() can get called:

const registration = await navigator.credentials.create({
  mediation: 'conditional'
  publicKey: {
    challenge: ...,
    user: ...,
    rp: ...,
  },
});

I like the fact that with this model the developer would only need to add (the same) mediation: 'conditional' property to an otherwise typical .create() call. And since the user would have to properly authenticate for the RP to then make the conditional registration call, I think it's easier for an RP dev to mentally map how to specify proper values for user.id, user.name, etc...

And from a browser maintainer perspective, it seems it might be easier to keep track of the conditionalCreate extension/flag being set even across hard page reloads, to then reset the flag the next time a the conditional .create() request is made...

I want to be clear that I think, at a high level, I like this proposal. My questions above are aiming to figure out what might be the best developer experience that'll remove as many footguns from this new capability if it gains wider support.

@nadalin nadalin added this to the L3-WD-01 milestone Aug 16, 2023
@nadalin nadalin added the @Risk Items that are at risk for L3 label Aug 16, 2023
@pascoej
Copy link
Contributor Author

pascoej commented Aug 16, 2023

@nicksteele

@MasterKale
Copy link
Contributor

There's nothing about this new capability that would dictate the kind of authentication that occurs before the conditional registration request, is there? I'm wondering if this would be a great migration path to help RP's move users from device-bound passkeys to synced passkeys, e.g. migrating users from macOS Chrome profile authenticator device-bound passkeys to synced iCloud Keychain passkeys 🤔

@madmath
Copy link

madmath commented Feb 22, 2024

I quite like this proposal as well, but as an operator of an IdP that doesn't rely on passwords, but rather on SMS or Email OTP as the only factor, I am wondering if we could extend the eligibility of conditional passkey registration to be applicable when the user agent autofills such OTP codes. Perhaps I missed this detail in the spec.

@pascoej
Copy link
Contributor Author

pascoej commented Feb 22, 2024

I quite like this proposal as well, but as an operator of an IdP that doesn't rely on passwords, but rather on SMS or Email OTP as the only factor, I am wondering if we could extend the eligibility of conditional passkey registration to be applicable when the user agent autofills such OTP codes. Perhaps I missed this detail in the spec.

I think this is allowed by the current spec text:

Note: |conditionalCreateLifetimeTimer| and |conditionalCreateOrigin| will be set by the user agent after it believes an authentication ceremony has
been completed and the user consents to this type of credential creation.

Auto-filling an OTP code could be considered part of an "authentication ceremony."

There's nothing about this new capability that would dictate the kind of authentication that occurs before the conditional registration request, is there?

No, there isn't for the same reason as above. It is the discretion of the user agent for what it can confidently recognize as an authentication ceremony. Autofill and WebAuthn assertions both sound like authentication ceremonies to me.

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

Successfully merging a pull request may close this issue.

7 participants