stages/identification: Add WebAuthn conditional UI (passkey autofill) support (#18377)

* add passkey_login to identification stage

* handle passkey auth in identification stage

* Add passkey settings in identification stage in the admin UI

* Add UI changes for basic passkey conditional login

* Fix linting

* rework

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests

* update admin form

* allow passing stage to validate_challenge_webauthn

* update flows/tests/test_inspector.py

* update for new field

* Fix linting

* update go solvers for identification challenge

* Refactor tests

* Skip mfa validation if user already authenticated via passkey at identification stage

* Add skip_if_passkey_authenticated option to authenticator validate stage and UI

* Add e2e test for passkey login conditional ui

* add policy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Remove skip_if_passkey_authenticated

* fix blueprint

* Set backend so password stage policy knows user is already authenticated

* Set backend so password stage policy knows user is already authenticated

* fix linting

* slight tweaks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* simplify e2e test

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Marcelo Elizeche Landó
2025-12-11 11:49:05 -03:00
committed by GitHub
parent 196bce348f
commit 15b93a5e9d
15 changed files with 484 additions and 13 deletions
@@ -60,6 +60,7 @@ entries:
order: 30
stage: !KeyOf default-authentication-mfa-validation
target: !KeyOf flow
id: default-authentication-flow-authenticator-validation-binding
model: authentik_flows.flowstagebinding
- identifiers:
order: 100
@@ -78,6 +79,18 @@ entries:
# If the user does not have a backend attached to it, they haven't
# been authenticated yet and we need the password stage
return not hasattr(flow_plan.context.get("pending_user"), "backend")
- model: authentik_policies_expression.expressionpolicy
id: default-authentication-flow-authenticator-validate-optional
identifiers:
name: default-authentication-flow-authenticator-validate-stage
attrs:
expression: |
flow_plan = request.context.get("flow_plan")
if not flow_plan:
return True
# if the authentication method is webauthn (passwordless), then we skip the authenticator
# validation stage by returning false (true will execute the stage)
return not (flow_plan.context.get("auth_method") == "auth_webauthn_pwl")
- model: authentik_policies.policybinding
identifiers:
order: 10
@@ -85,3 +98,10 @@ entries:
policy: !KeyOf default-authentication-flow-password-optional
attrs:
failure_result: true
- model: authentik_policies.policybinding
identifiers:
order: 10
target: !KeyOf default-authentication-flow-authenticator-validation-binding
policy: !KeyOf default-authentication-flow-authenticator-validate-optional
attrs:
failure_result: true
+5
View File
@@ -14508,6 +14508,11 @@
"type": "boolean",
"title": "Enable remember me",
"description": "Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password."
},
"webauthn_stage": {
"type": "integer",
"title": "Webauthn stage",
"description": "When set, and conditional WebAuthn is available, allow the user to use their passkey as a first factor."
}
},
"required": []