Skip to content

Conversation

@kenrb
Copy link

@kenrb kenrb commented May 6, 2025

This specifies the behaviour of the mediation: 'immediate'.

Issue: #2228
Explainer: https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-immediate-mediation

Parallel PR adding the enum to Credential Management: w3c/webappsec-credential-management#272


Preview | Diff

kenrb and others added 6 commits April 29, 2025 20:56
This PR adds the Immediate Mediation mode for WebAuthn requests,
which shows discoverable credentials to the user if any are
silently found, or else throws a `NotAllowedError` if there are
none.

See the
[explainer](https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-immediate-mediation) for more context.
A parallel PR to add the mode to the Credential Management
specification will be created.
@kopy
Copy link

kopy commented May 8, 2025

While I understand the privacy considerations underlying AllowList limitations, requiring explicit user activation significantly restricts practical usability at scale for this scenario:

  • Direct use on standard login pages (typical scenario for most websites) is the main issue addressed here. If an email input field is already visible, requiring further user activation for immediate mediation becomes unintuitive, as the user may have already started typing their email. A lot of websites have a profile or menu and not a "sign-in button" on the upper right or use "layers"

Removing the strict requirement for explicit user activation, aligning with existing first-party WebAuthn implementations, would substantially enhance usability. Immediate mediation could then seamlessly activate upon page rendering, offering a smoother authentication experience before users interact with input fields. Notably, every native implementation that supports preferImmediatelyAvailable—such as on Android and iOS—do not require a user activation.

Currently, adopting this approach broadly would necessitate extensive UI and workflow adjustments. Furthermore, login pages often redirect users to relying-party single sign-on systems, making UI changes challenging and potentially disrupting existing authentication workflows.

@Firstyear
Copy link
Contributor

Having webauthn prompts randomly trigger on a page load with no explanation or context for users, sounds like the exact opposite of good UX to me - it sounds a lot more like a path to confusion and frustration.

@kopy
Copy link

kopy commented May 8, 2025

Having webauthn prompts randomly trigger on a page load with no explanation or context for users, sounds like the exact opposite of good UX to me - it sounds a lot more like a path to confusion and frustration.

This could still happen after the user navigates to the login page or exactly where it does now. It’s not random we leave it up to RPs - we had this exact same dicussion with WebAuthn user gestures for Safari and they were lifted. As someone responsible for large consumer RP implementations, I have problems seeing clearly how this approach helps for most pages.

@kenrb
Copy link
Author

kenrb commented May 9, 2025

This could still happen after the user navigates to the login page or exactly where it does now. It’s not random we leave it up to RPs - we had this exact same dicussion with WebAuthn user gestures for Safari and they were lifted. As someone responsible for large consumer RP implementations, I have problems seeing clearly how this approach helps for most pages.

The main advantage of not having a user gesture requirement for existing modal WebAuthn calls is that they can be used for re-auth, a use case for which immediate mediation isn't useful.

Immediate is aimed at scenarios in which a user has done something to indicate a sign-in is appropriate at that time. This isn't precisely replicating preferImmediatelyAvailableCredentials on mobile because the web has different privacy properties.

There is a separate proposal for a mode called Ambient, in which more subtle (non-modal) UI is displayed to offer the user an opportunity to sign-in, and would not require user activation. https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-Ambient-Signin-UI

That proposal is still active.

@kopy
Copy link

kopy commented May 12, 2025

This could still happen after the user navigates to the login page or exactly where it does now. It’s not random we leave it up to RPs - we had this exact same dicussion with WebAuthn user gestures for Safari and they were lifted. As someone responsible for large consumer RP implementations, I have problems seeing clearly how this approach helps for most pages.

The main advantage of not having a user gesture requirement for existing modal WebAuthn calls is that they can be used for re-auth, a use case for which immediate mediation isn't useful.

I see your point, but my comment referred to the fact that continuously triggering WebAuthn requests hasn’t yet emerged as a significant abuse issue. WebKit also moved away from enforcing user gestures for WebAuthn, recognizing that plenty of alternative approaches are available to effectively rate-limit such behavior. For example, I can see why this is useful in cross-origin iframes.

Immediate is aimed at scenarios in which a user has done something to indicate a sign-in is appropriate at that time. This isn't precisely replicating preferImmediatelyAvailableCredentials on mobile because the web has different privacy properties.

There is a separate proposal for a mode called Ambient, in which more subtle (non-modal) UI is displayed to offer the user an opportunity to sign-in, and would not require user activation. https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-Ambient-Signin-UI

That proposal is still active.

I am aware; thank you, @kenrb. I greatly appreciate Chrome’s efforts to improve the passkey experience. Immediate mediation is helpful, but only precisely for the UI case you mentioned - it’s just challenging for some RP implementations. I was simply suggesting making it more broadly applicable; perhaps the ambient proposal would be better suited.

@zacknewman
Copy link
Contributor

Do we want an error to occur if an RP uses immediate mediation during credential registration, or is it sufficient to effectively treat it as an unknown enum value which in turn is treated as required?

kenrb added 2 commits November 5, 2025 16:29
Added uiMode. Also expanded the privacy section.
@nsatragno nsatragno self-requested a review February 3, 2026 15:07
@@ -2266,10 +2266,10 @@
{{PublicKeyCredential}}'s implementation of {{PublicKeyCredential/[DISCOVER-METHOD]}} is specified in the next section.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the PR description now that the parameter is known as "uiMode".


1. Let |silentlyDiscoveredCredentials| be a new [=map=] whose [=map/entry|entries=] are of the form: [=DiscoverableCredentialMetadata=] → [=authenticator=].

If <code>|options|.{{CredentialRequestOptions/uiMode}}</code> is present with the value {{CredentialUiModeRequirement/immediate}},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour for this timer should be well specified. It's good that we're allowing flexibility in choosing the timer, but the behaviour of the timer should be well specified instead of relying on a note.

One way to solve this would be to start a timer here. Then, during the lifetimeTimer loop below, when this timer expires, check if |silentlyDiscoveredCredentials| is empty. If non empty, render the UI. This will also help solve a problem with the way the UI is being triggered at the moment.

You can also add that once all plugged in authenticators have had their credentials discovered, we don't wait for the timeout anymore.

: If an |authenticator| becomes available on this [=client device=],
:: Note: This includes the case where an |authenticator| was available upon |lifetimeTimer| initiation.

1. If <code>|options|.{{CredentialRequestOptions/mediation}}</code> is {{CredentialMediationRequirement/conditional}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UA should also run this operation if uiMode is immediate, otherwise |collectedDiscoveredCredentialMetadata| will always be empty.

1. [=set/Append=] |authenticator| to |issuedRequests|.

: If <code>|options|.{{CredentialRequestOptions/mediation}}</code> is not {{CredentialMediationRequirement/conditional}},
: If <code>|options|.{{CredentialRequestOptions/uiMode}}</code> is present with value {{CredentialUiModeRequirement/immediate}},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step is inside an infinite while loop, so as written this will trigger continuously. Instead, I think what you want is to trigger this UI once the immediate mediation timer runs out. So something like

"If options.uiMode is present with value immediate and either immediateModeTimer is expired or all plugged in authenticators have had their credentials enumerated,"


: If <code>|options|.{{CredentialRequestOptions/mediation}}</code> is not {{CredentialMediationRequirement/conditional}},
: If <code>|options|.{{CredentialRequestOptions/uiMode}}</code> is present with value {{CredentialUiModeRequirement/immediate}},
1. If |silentlyDiscoveredCredentials| is not [=list/empty=]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: These steps are very deep. Let's invert the condition and change this to an early throw.


1. Throw a "{{NotAllowedError}}" {{DOMException}}.

1. Else,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you don't need this else after the throw.

fingerprinting risk across many calls with varied [=Relying Party Identifier|RP IDs=]. Requests using {{CredentialUiModeRequirement/immediate}}
UI mode leak this information in some situations because there is a difference in timing between when the method is invoked and the promise
resolves:
- If it resolves in a short time, then it might mean that the user has no immediately available credentials for the given Relying Party. There
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for the given [=[RP]=]


1. [=Consume user activation=] of the [=relevant global object=].

1. If the user agent is in a private browsing mode throw a "{{NotAllowedError}}" {{DOMException}}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wait for the timeout / all credentials being enumerated before throwing if the user is in private browsing mode? Otherwise we risk leaking the fact the user is in private mode by returning much faster than when there are no credentials.

Since Immediate UI mode will not show UI in private browsing modes, it is important that failures be handled in a way that does not reveal
the presence of a private browsing mode to the Relying Party. A request invoked from a private browsing mode should not be distinguishable
from a request that did not find any available credentials. Therefore:
- The user agent should perform credential enumeration and any other tasks that require time before rejecting the promise.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this isn't what the steps above are doing. I think if we simply set the discovered credential map to empty right before showing the immediate UI if the user is in private mode then we get this effect.

@kenrb kenrb changed the title Add Immediate Mediation Add Immediate uiMode Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants