Open shotor opened 3 months ago
I'm just setting this up again myself, looks like you need to:
pending_user supports webauthn
policy via the edit binding button.That works! Thanks.
I think this probably not the the best practice though security wise. This method also allows TOTP as a passwordless login method, where I think you really want just webauthn as the passwordless method.
I'll try the "passwordless flow" setting on the authentication flow.
Issue may be closed if you wish!
Ah that is a very good point. Just adjust the policy to be explicit with True/False (uncheck the Negate the pending_user supports webauthn
policy via the edit binding button.)
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
if WebAuthnDevice.objects.filter(user=request.context['pending_user'], confirmed=True).exists():
return False # Bypass password auth
else:
return True # Proceed to password auth
It looks to me that the Doc's need updating
Ah that is a very good point. Just adjust the policy to be explicit with True/False (uncheck the Negate the
pending_user supports webauthn
policy via the edit binding button.)from authentik.stages.authenticator_webauthn.models import WebAuthnDevice if WebAuthnDevice.objects.filter(user=request.context['pending_user'], confirmed=True).exists(): return False # Bypass password auth else: return True # Proceed to password auth
It looks to me that the Doc's need updating
Unfortunately that gives the same result using the default-mfa-validation
stage. That stage contains both Webauthn and TOTP as valid MFA methods.
To do it all in the same flow; I think you'd need a separate webauthn-only-mfa-validation
stage.
Not sure how to program it or if it's even possible, but would have to be something like this:
Welcome to Authentik!
|
Stage (Identification Stage)
|
├─ Policy (has webauthn true)
| └─ Stage (Authenticator Validation) NEW webauthn only
| └─ Stage (User Login Stage) default-authentication-login
| └─ End of the flow
|
└─ Policy (has webauthn false)
└─ Stage (Password Stage) default-authentiation-password
└─ Stage (Authenticator Validation) default-authentication-mfa-validation (totp only available if you got this far)
└─ Stage (User Login Stage) default-authentication-login
└─ End of the flow
Where:
- Stage (Authenticator Validation) NEW webauthn only
- Stage (Authenticator Validation) default-authentication-mfa-validation (totp only available if you got this far)
Point to the same Stage (User Login Stage) default-authentication-login
Then again there's this option, which seems to add a "passwordless" button to your login form:
That stage contains both Webauthn and TOTP as valid MFA methods.
Ah sorry I miss-read that, I thought you were able to enter TOTP instead of the password. If you really want to skip TOTP for Webauthn I also suggest as you said that you would need to create a second MFA stage with only Webauthn, add it to the flow then use a policy on both the original and new stage to either pass or use that stage depending on the Webauthn exists state.
But as you said, you can just add the password-less flow button. It's a touch less user friendly but gets the job done. At any rate this isn't a bug and should be closed.
Just wanted to say thank you to all participants in this thread. I wanted to make the flow that @shotor describes 2 comments above, but his work didn't work exactly as I wanted, so with a bit of tinkering I did this:
It works wonders (if the user you supply exists), with a bit of tinkering I should be able to make it not error with unknown users.
Just wanted to say thank you to all participants in this thread. I wanted to make the flow that @shotor describes 2 comments above, but his work didn't work exactly as I wanted, so with a bit of tinkering I did this: ... It works wonders (if the user you supply exists), with a bit of tinkering I should be able to make it not error with unknown users.
@SpiderD555
Maybe that error is good. Otherwise you'd have to validate the user actually exists which means someone can just try different users to see which one exists.
If you want to validate the user there's a flag on default-authentication-identification
. That might solve the error with unknown user but I don't know if that's what you want.
Pretend user exists
When enabled, the stage will always accept the given user identifier and continue.
Would you mind sharing your stage bindings with me? So I can more easily reverse engineer it.
@shotor
Maybe that error is good. Otherwise you'd have to validate the user actually exists which means someone can just try different users to see which one exists.
I wonder about that. Once you make a typo in the username, then you have to go through a bother of clearing cookies in the browser to fix it. Moreover each such situation generates an error notification in admin interface. I have just managed to make it work without error, see details below.
Would you mind sharing your stage bindings with me? So I can more easily reverse engineer it.
Here is the generic configuration:
As for policy configurations ... "pending user supports webauthn" has this statement:
if ak_user_by(username=request.context['pending_user']) != None:
if ak_user_has_authenticator(request.context['pending_user'], device_type = "webauthn"):
return True # Bypass password auth
else:
return False # Proceed to password auth
else:
return False # Proceed to password auth
"pending user does not support webauthn" has this statement:
if ak_user_by(username=request.context['pending_user']) != None:
if not ak_user_has_authenticator(request.context['pending_user'], device_type = "webauthn"):
return True # Proceed to password auth
else:
return False # Bypass password auth
else:
return True # Proceed to password auth
Notice how both policies are very similar, second one has a reverse logic, so it only sends you to password flow, when webauthn is not configured for the user (or when user does not exists).
This is my webauthn-validator stage (it's mostly a copy paste from default MFA validator):
=======
Edit: Updated policies that work without errors.
Thank you @SpiderD555 That's amazing!!
I think we should update the docs. The configuration in the docs section doesn't work at all. @dugite-code what do you think of the solution proposed by @SpiderD555 ?
It looks like what I had in mind with what we were talking about above, makes sense there would be an issue when the user didn't exist. Nicely fixed @SpiderD555, when you have time maybe you could open a pull request against the docs and see if the authentik team would approve it? it's a simple static site generator so it isn't too difficult.
@shotor @dugite-code Can you check if the exported flow loads for you ? I am just a bit worried that I may be using non-default flow elements, and as a result it won't work if it is loaded into non-default Authentik instance. If that's the case it wouldn't make sense to post it as an example in docs.
Just copy the script into "flows-login-webauthn-password.yaml" file and try to import it
context: {}
entries:
- attrs:
authentication: none
denied_action: message_continue
designation: authentication
layout: stacked
name: webauthn-password-flow
policy_engine_mode: any
title: webauthn-password-flow
conditions: []
id: null
identifiers:
pk: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb
slug: webauthn-password-flow
model: authentik_flows.flow
state: present
- attrs:
expression: "if ak_user_by(username=request.context['pending_user']) != None:\n\
\ if not ak_user_has_authenticator(request.context['pending_user'], device_type\
\ = \"webauthn\"):\n return True # Proceed to password auth\n else:\n \
\ return False # Bypass password auth\nelse:\n return True # Proceed to password\
\ auth"
name: pending user does not support webauthn
conditions: []
id: null
identifiers:
pk: 831a2eed-3d25-48ff-9bd7-596494ab83a9
model: authentik_policies_expression.expressionpolicy
state: present
- attrs:
expression: "if ak_user_by(username=request.context['pending_user']) != None:\n\
\ if ak_user_has_authenticator(request.context['pending_user'], device_type\
\ = \"webauthn\"):\n return True # Bypass password auth\n else:\n return\
\ False # Proceed to password auth\nelse:\n return False # Proceed to password\
\ auth"
name: pending user supports webauthn
conditions: []
id: null
identifiers:
pk: 0f358213-f839-4fd7-99e8-7a777341c66f
model: authentik_policies_expression.expressionpolicy
state: present
- attrs:
geoip_binding: no_binding
network_binding: no_binding
remember_me_offset: seconds=0
session_duration: seconds=0
conditions: []
id: null
identifiers:
name: default-authentication-login
pk: c15babcd-bbf6-470d-80a0-ebc92ecaec6f
model: authentik_stages_user_login.userloginstage
state: present
- attrs:
backends:
- authentik.core.auth.InbuiltBackend
- authentik.sources.ldap.auth.LDAPBackend
- authentik.core.auth.TokenBackend
configure_flow: 63204909-a37d-4dc1-ab3d-92fd141d6569
failed_attempts_before_cancel: 5
conditions: []
id: null
identifiers:
name: default-authentication-password
pk: 3ce948fd-8741-4205-b5d0-5e72a9600de7
model: authentik_stages_password.passwordstage
state: present
- attrs:
case_insensitive_matching: true
pretend_user_exists: true
show_matched_user: true
user_fields:
- username
- email
conditions: []
id: null
identifiers:
name: default-authentication-identification
pk: 0f9390c6-c469-4a73-b419-dfc3e617e741
model: authentik_stages_identification.identificationstage
state: present
- attrs:
configuration_stages:
- 4561d2b1-1699-4977-8367-944437e84bd3
device_classes:
- webauthn
last_auth_threshold: seconds=0
not_configured_action: deny
webauthn_user_verification: required
conditions: []
id: null
identifiers:
name: webauthn-validation
pk: 96aff095-0b17-44c9-ad95-e035d4e5cd7f
model: authentik_stages_authenticator_validate.authenticatorvalidatestage
state: present
- attrs:
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: true
conditions: []
id: null
identifiers:
order: 10
pk: 26096990-bec2-4349-97ab-6d2267dd573c
stage: 0f9390c6-c469-4a73-b419-dfc3e617e741
target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb
model: authentik_flows.flowstagebinding
state: present
- attrs:
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: true
conditions: []
id: null
identifiers:
order: 20
pk: 07f1e191-21c8-4e83-8342-1769a10fb395
stage: 96aff095-0b17-44c9-ad95-e035d4e5cd7f
target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb
model: authentik_flows.flowstagebinding
state: present
- attrs:
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: true
conditions: []
id: null
identifiers:
order: 30
pk: a3ab64e4-865c-4d58-b35b-552284e26b38
stage: 3ce948fd-8741-4205-b5d0-5e72a9600de7
target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb
model: authentik_flows.flowstagebinding
state: present
- attrs:
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: true
conditions: []
id: null
identifiers:
order: 40
pk: fb4b2e1a-97ca-45c4-b2f3-1938f39f124a
stage: c15babcd-bbf6-470d-80a0-ebc92ecaec6f
target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb
model: authentik_flows.flowstagebinding
state: present
- attrs:
enabled: true
timeout: 30
conditions: []
id: null
identifiers:
order: 0
pk: 61a6d543-2b83-4fa6-8f6a-9d7918d017e0
policy: 0f358213-f839-4fd7-99e8-7a777341c66f
target: 07f1e191-21c8-4e83-8342-1769a10fb395
model: authentik_policies.policybinding
state: present
- attrs:
enabled: true
timeout: 30
conditions: []
id: null
identifiers:
order: 0
pk: 768d6097-41c9-427e-a24b-1e204a03e935
policy: 831a2eed-3d25-48ff-9bd7-596494ab83a9
target: a3ab64e4-865c-4d58-b35b-552284e26b38
model: authentik_policies.policybinding
state: present
metadata:
labels:
blueprints.goauthentik.io/generated: 'true'
name: authentik Export - 2024-04-08 10:25:41.303872+00:00
version: 1
@shotor @dugite-code Can you check if the exported flow loads for you ? I am just a bit worried that I may be using non-default flow elements, and as a result it won't work if it is loaded into non-default Authentik instance. If that's the case it wouldn't make sense to post it as an example in docs.
Just copy the script into "flows-login-webauthn-password.yaml" file and try to import it
context: {} entries: - attrs: authentication: none denied_action: message_continue designation: authentication layout: stacked name: webauthn-password-flow policy_engine_mode: any title: webauthn-password-flow conditions: [] id: null identifiers: pk: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb slug: webauthn-password-flow model: authentik_flows.flow state: present - attrs: expression: "if ak_user_by(username=request.context['pending_user']) != None:\n\ \ if not ak_user_has_authenticator(request.context['pending_user'], device_type\ \ = \"webauthn\"):\n return True # Proceed to password auth\n else:\n \ \ return False # Bypass password auth\nelse:\n return True # Proceed to password\ \ auth" name: pending user does not support webauthn conditions: [] id: null identifiers: pk: 831a2eed-3d25-48ff-9bd7-596494ab83a9 model: authentik_policies_expression.expressionpolicy state: present - attrs: expression: "if ak_user_by(username=request.context['pending_user']) != None:\n\ \ if ak_user_has_authenticator(request.context['pending_user'], device_type\ \ = \"webauthn\"):\n return True # Bypass password auth\n else:\n return\ \ False # Proceed to password auth\nelse:\n return False # Proceed to password\ \ auth" name: pending user supports webauthn conditions: [] id: null identifiers: pk: 0f358213-f839-4fd7-99e8-7a777341c66f model: authentik_policies_expression.expressionpolicy state: present - attrs: geoip_binding: no_binding network_binding: no_binding remember_me_offset: seconds=0 session_duration: seconds=0 conditions: [] id: null identifiers: name: default-authentication-login pk: c15babcd-bbf6-470d-80a0-ebc92ecaec6f model: authentik_stages_user_login.userloginstage state: present - attrs: backends: - authentik.core.auth.InbuiltBackend - authentik.sources.ldap.auth.LDAPBackend - authentik.core.auth.TokenBackend configure_flow: 63204909-a37d-4dc1-ab3d-92fd141d6569 failed_attempts_before_cancel: 5 conditions: [] id: null identifiers: name: default-authentication-password pk: 3ce948fd-8741-4205-b5d0-5e72a9600de7 model: authentik_stages_password.passwordstage state: present - attrs: case_insensitive_matching: true pretend_user_exists: true show_matched_user: true user_fields: - username - email conditions: [] id: null identifiers: name: default-authentication-identification pk: 0f9390c6-c469-4a73-b419-dfc3e617e741 model: authentik_stages_identification.identificationstage state: present - attrs: configuration_stages: - 4561d2b1-1699-4977-8367-944437e84bd3 device_classes: - webauthn last_auth_threshold: seconds=0 not_configured_action: deny webauthn_user_verification: required conditions: [] id: null identifiers: name: webauthn-validation pk: 96aff095-0b17-44c9-ad95-e035d4e5cd7f model: authentik_stages_authenticator_validate.authenticatorvalidatestage state: present - attrs: invalid_response_action: retry policy_engine_mode: any re_evaluate_policies: true conditions: [] id: null identifiers: order: 10 pk: 26096990-bec2-4349-97ab-6d2267dd573c stage: 0f9390c6-c469-4a73-b419-dfc3e617e741 target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb model: authentik_flows.flowstagebinding state: present - attrs: invalid_response_action: retry policy_engine_mode: any re_evaluate_policies: true conditions: [] id: null identifiers: order: 20 pk: 07f1e191-21c8-4e83-8342-1769a10fb395 stage: 96aff095-0b17-44c9-ad95-e035d4e5cd7f target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb model: authentik_flows.flowstagebinding state: present - attrs: invalid_response_action: retry policy_engine_mode: any re_evaluate_policies: true conditions: [] id: null identifiers: order: 30 pk: a3ab64e4-865c-4d58-b35b-552284e26b38 stage: 3ce948fd-8741-4205-b5d0-5e72a9600de7 target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb model: authentik_flows.flowstagebinding state: present - attrs: invalid_response_action: retry policy_engine_mode: any re_evaluate_policies: true conditions: [] id: null identifiers: order: 40 pk: fb4b2e1a-97ca-45c4-b2f3-1938f39f124a stage: c15babcd-bbf6-470d-80a0-ebc92ecaec6f target: d732b59f-14ab-4ef1-a5e7-983d2b7e40fb model: authentik_flows.flowstagebinding state: present - attrs: enabled: true timeout: 30 conditions: [] id: null identifiers: order: 0 pk: 61a6d543-2b83-4fa6-8f6a-9d7918d017e0 policy: 0f358213-f839-4fd7-99e8-7a777341c66f target: 07f1e191-21c8-4e83-8342-1769a10fb395 model: authentik_policies.policybinding state: present - attrs: enabled: true timeout: 30 conditions: [] id: null identifiers: order: 0 pk: 768d6097-41c9-427e-a24b-1e204a03e935 policy: 831a2eed-3d25-48ff-9bd7-596494ab83a9 target: a3ab64e4-865c-4d58-b35b-552284e26b38 model: authentik_policies.policybinding state: present metadata: labels: blueprints.goauthentik.io/generated: 'true' name: authentik Export - 2024-04-08 10:25:41.303872+00:00 version: 1
@SpiderD555 I just tried to import this into my Authentik service, which only has one additional flow added for recovery email. Unfortunately it fails at the authentik.blueprints.v1.importer stage with an object does not exist error at the backends section.
Entry invalid: Serializer errors {'configure_flow': [ErrorDetail(string='Invalid pk "63204909-a37d-4dc1-ab3d-92fd141d6569" - object does not exist.', code='does_not_exist')]}
I was able to follow your earlier post's details though and get this set up and working within minutes, thanks. When exporting mine the pk's are different, that's the only thing. I'm new to Authentik so I'm not sure if there's anything that can be done to avoid the conflict in pks.
@SpiderD555 one issue I have found is that there's no way to log in with a password if you have an issue with the existing passkey for an account. In my case I set one up using Bitwarden on PC via the extension but on my Samsung phone I am prompted to use the passkey (that I don't have) on my device. There's no method for me to add an additional passkey on the device at this point as I can't get to the password entry. I suspect that adding 'Use a password instead' as a link won't work unless there's a way to pass a parameter to override the 'pending user supports webauthn' to it too.
EDIT: Actually this is an issue with the default implementation of webauthn. I cannot log into authentik with my phone if the first passkey I created was in Bitwarden on my PC. Bitwarden is bringing passkeys to Andorid soon so I'll figure out a workaround for now.
Describe the bug
I followed this guide https://docs.goauthentik.io/docs/flow/stages/password/
But after setting it up it still prompts for my password. After entering my password I can select my webauthn device.
To Reproduce Steps to reproduce the behavior:
Make sure you have webauthn device enabled
pending_user supports webauthn
with contents:default-authentication-flow
state bindings
default-authentication-password
bind existing policy
pending_user supports webauthn
, order 0, timeout 30, don't pass failure resultYou will now see a password prompt
Expected behavior
It should allow me to choose my webauthn device instead of the password prompt
Screenshots
Logs
Version and Deployment (please complete the following information):
Additional context
Flow looks weird, feel like a step is missing