improve WebAuthn auth w/ auto-modal on page load#46960
Conversation
|
sorry, deleted the branch by mistake |
|
Just a side note from a developer experience: extending Keycloak's admin UI is more than hard if you are not a very well experienced JS developer. You should definitely improve here and make it easier for everyone to extend/contribute! |
|
@keycloak/core-clients - can you help out @dasniko with the test, and also comment on if this should be configurable as he describes, or if it could be the new default? |
|
@mposolda @mabartos @rmartinc Would you mind having a look into this PR and the detailed explanation in the corresponding issue and give me a first feedback!? I can remove the switch again, or make it a select-box, as there are several options according to the spec (see #46959 (comment)). Currently, also the documentation is still missing. I will add this once we decided how to move on. I'd really appreciate to get this merged into core, as I see this behavior as a default on more and more websites using passkeys! |
|
Thanks @dasniko for the PR! Trying to answer your questions:
I see the switch OK if we don't want to support more options. But, on the other hand, if later the spec defines more options (for example the
I would maintain the same default conditional for the moment, even the default is optional in the spec. In order to not change the current behavior. I'm open if you think different, but initially I wouldn't change the current default behavior. Regarding the tests, the tests for passkeys are currently in the old testsuite in this package. I'm moving part of them to the new TS in this other PR #47940. But I don't know if we can test this, because the selenium configuration (when using mediation) always performs the login automatically using the passkey, so probably both options (conditional and optional) will work in the same way. But, at least, one of the tests (for example the default username/password |
|
@rmartinc Thanks for your response. Regarding the tests, I can see in your PR that |
|
@dasniko yes, it makes sense, but I don't know when PR #47940 will be reviewed and merged. Let's see if it does not take much time. You can just duplicate the discoverable tests (for example |
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.model.session.UserSessionProviderModelTest#testCreateUserSessionsParallelKeycloak CI - Store Model Tests org.keycloak.testsuite.model.session.UserSessionProviderModelTest#testCreateUserSessionsParallelKeycloak CI - Store Model Tests |
|
@rmartinc I've extended your tests in the new TS and made them a The failing JavaScript tests/checks are not related to my changes, seems there's a failure with an npm lib!? The new flaky tests are also not related to my changes. |
rmartinc
left a comment
There was a problem hiding this comment.
Thanks @dasniko! After testing this, I have some comments and a general question.
I don't like much the user experience in one aspect after this change. Set to optional for example, if the user decide to close the webauthn modal, the modal is shown again and again when the user performs any action, for example the user fails in the password. It's a bit annoying.
I don't know if we need to add something to not show the modal if the user declines to use the passkey when the same auth session. One possible solution was using the cookie KC_AUTH_SESSION_HASH and storing something in the local store. But I don't know if I'm over-reacting.
@mabartos Could you please review this PR? Just to get a second opinion on this.
There was a problem hiding this comment.
Pull request overview
This PR enhances passkey (WebAuthn) login UX on username-based login forms by introducing configurable mediation behavior (including auto-triggering the native passkey modal on page load) and consolidating shared WebAuthn JS helpers to reduce duplication.
Changes:
- Add a new Passkey Mediation setting to the WebAuthn Passwordless Policy (server model, persistence, admin UI, docs) and propagate it to login templates.
- Refactor WebAuthn theme JavaScript to export shared
doAuthenticate/getAllowCredentialshelpers and implement sequential mediation behavior in the passkeys conditional flow. - Extend webauthn passwordless tests to cover both
conditionalandoptionalmediation modes.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| themes/src/main/resources/theme/base/login/resources/js/webauthnAuthenticate.js | Refactors auth to use an input object, exports shared doAuthenticate/getAllowCredentials, and adds support for passing mediation/additional options. |
| themes/src/main/resources/theme/base/login/resources/js/passkeysConditionalAuth.js | Implements sequential mediation logic (optional/required/silent/conditional) using shared WebAuthn helpers. |
| themes/src/main/resources/theme/base/login/passkeys.ftl | Passes configured mediation value to the JS init args (defaulting to conditional). |
| tests/webauthn/src/test/java/org/keycloak/tests/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java | Parameterizes discoverable-key login test for conditional and optional mediation. |
| tests/webauthn/src/test/java/org/keycloak/tests/webauthn/passwordless/PasskeysUsernameFormTest.java | Parameterizes discoverable-key login test for conditional and optional mediation. |
| test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java | Adds builder method to set passwordless mediation in realm test configuration. |
| services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java | Exposes passwordless policy mediation to the login form template attributes. |
| services/src/main/java/org/keycloak/WebAuthnConstants.java | Adds a new template attribute constant for mediation. |
| server-spi/src/main/java/org/keycloak/models/WebAuthnPolicy.java | Adds mediation field with getter/setter to the policy model. |
| server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java | Exports passwordless mediation into RealmRepresentation. |
| server-spi-private/src/main/java/org/keycloak/models/WebAuthnPolicyTwoFactorDefaults.java | Adds mediation default handling in the read-only defaults implementation. |
| model/storage-private/src/main/java/org/keycloak/storage/datastore/DefaultExportImportManager.java | Imports/exports passwordless mediation for realm import/export flows. |
| model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmAttributes.java | Adds realm-attribute key constant for mediation persistence. |
| model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java | Reads/writes mediation into realm attributes as part of WebAuthn policy persistence. |
| js/apps/admin-ui/src/authentication/policies/WebauthnPolicy.tsx | Adds Passkey Mediation select to the admin UI (visible when passkeys are enabled). |
| js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties | Adds UI labels/help + option strings for mediation. |
| docs/documentation/server_admin/topics/authentication/passkeys.adoc | Documents Passkey Mediation semantics and available values. |
| core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java | Adds webAuthnPolicyPasswordlessMediation field for admin REST representation. |
|
@rmartinc Thanks for your feedback, valid points, also from CoPilot. I just pushed some fixes/improvements.
I understand your concerns. And I did some tests on other websites I know using the |
There was a problem hiding this comment.
I understand your concerns. And I did some tests on other websites I know using the optional mediation directly. They behave the same: every time I get on the login page, no matter why, and I do have a registered passkey for this domain, I'll get the modal.
For me personally, that's not a problem, because if I do have a registered passkey for a domain/website, I do want to use it, I don't want to use other credential methods. But perhaps that's only my personal opinion!?
So, I'm completely open to whatever. IMHO this is not required, but if you decide to go with a proposed storage solution (I'd prefer the session storage, no the local storage), I'll also fine with it and will implement it.
Thanks @dasniko! For me it's OK now except for the part you commented above. I' don't know what I prefer either. So let see what @mabartos thinks, if he thinks we are OK always presenting the modal, we'll go with it.
And yes, if we need to hide the modal after cancel, use the session storage better, I just commented the first idea that came to my mind. 😄
Maybe I do some minor additions to the documentation. But better suggest that after we decide what to do with the modal.
|
@dasniko I've tried the solution, and first of all, very nice job! However, I find the opening of the modal annoying too :/ I think we can be opinionated in this case, and rather improve the UX than provide a standard way of how others do it. For the default browser flow(UnPwdForm), when the user puts in a wrong password (it happens many times; count me in as well), the modal is shown again and again - and it is very annoying.
@dasniko If you already have a registered passkey for the domain, it's somewhat expected that you'll use it, as it's almost frictionless. However, when there's a cross-platform device (like my phone), I don't want to be bothered by the modal so many times either (we need to show the modal for the first time to check if the cross-platform device has it registered). So, if the passwordless policy allows only the platform passkeys, and there's the registered passkey, I'm ok to show the modal again even when the user puts a wrong password, as the passkey could be perceived as a much better and easier method to log in. But, there might be some other user trying to log in from the same device (public places, library,...) - So, even maybe in this case, I'd not always show the modal (even when the password is incorrectly put). Summary: I think it'd be good to show it once(per authn session?), and when the user clicks 'Cancel', just do not show it again. If they want to change their minds, they still can use the button 'Sign in with passkeys', which has great visibility on the login page. So, agree with the @rmartinc view on this :) But it's just my 2 cents. WDYT? |
As already mentioned, I'm totally fine with keeping the modal closed if the user closes it manually. So, I'm going to implement it. |
|
Done, I added the dismissal check. |
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.model.singleUseObject.SingleUseObjectModelTest#testClusterKeycloak CI - Store Model Tests org.keycloak.testsuite.model.user.UserModelTest#testAddDirtyRemoveFederationUsersInTheSameGroupConcurrentKeycloak CI - Store Model Tests org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest#testOnRealmRemovedKeycloak CI - Store Model Tests |
|
JFYI - As a follow-up, we could also not show the modal when the 'Cross-platform' passkeys are disabled, and there's no platform authenticator available ( Because what's the reason to show the modal when we know only the platform authenticator can be used, but there's no way to use it? |
Yeah, makes sense, IMHO. |
rmartinc
left a comment
There was a problem hiding this comment.
@dasniko My final changes. I prefer to just maintain one key in the store. WDYT about my proposal? The other change is just a reword of the note. I want to make clear that the mediation option its out of keycloak's control, we just can pass the option. With this I'm OK.
Yes, we don't want to bother you more in this PR. We will create another issue if we think more checks are needed. |
|
Hopefully everything is now contained. |
Related to this: @dasniko Could you please resolve the conflict? Thanks! |
…tion possible values: conditional, optional, required, silent conditional remains the default to not break the current behavior when optional or required and the user dismissed the modal, it will stay hidden for this auth-session, can still be opened by button adjusted all related resources, like JS files (also consolidated duplicated logic), Java classes and freemarker template tests extended passkey documentation extended/updated closes keycloak#46959 Signed-off-by: Niko Köbler <niko@n-k.de>
closes #46959