authentik: Passkey-only Login für Home Assistant und NetBird #115

Merged
k.renner merged 2 commits from feat/authentik-passkey-stepup into master 2026-07-07 07:14:15 +00:00
2 changed files with 179 additions and 4 deletions
@@ -612,3 +612,178 @@ data:
identifiers:
name: Caddy docs.renner.uno
state: absent
# Passkey-only (WebAuthn) login for selected apps, in two parts:
#
# 1. A passwordless AUTHENTICATION flow (username → passkey, no
# password prompt) bound per-provider, so fresh logins to these
# apps never see a password field. auth.renner.uno's own login
# (default brand flow) is untouched.
# 2. A step-up AUTHORIZATION flow — unlike the authentication flow,
# this runs on EVERY interactive authorize, so an existing
# password-only SSO session from another app cannot slip into
# these apps without presenting a passkey.
passkey-stepup.yaml: |
version: 1
metadata:
name: Passkey step-up authorization (homelab)
entries:
- id: passkey-stepup-flow
model: authentik_flows.flow
identifiers:
slug: passkey-stepup-authorization
state: present
attrs:
name: Passkey step-up authorization
title: "Redirecting to %(app)s"
designation: authorization
# Unauthenticated users are bounced through the default
# authentication flow first, then land back here for the
# WebAuthn validation — same as the built-in implicit-consent
# flow this replaces.
authentication: require_authenticated
- id: passkey-stepup-webauthn-validation
model: authentik_stages_authenticator_validate.authenticatorvalidatestage
identifiers:
name: passkey-stepup-webauthn-validation
state: present
attrs:
device_classes:
- webauthn
# `configure`, not `deny`: g.renner has Home Assistant access
# but no enrolled WebAuthn device (live check 2026-07-07), so
# a hard deny would lock her out with no self-service path.
# `configure` injects the default WebAuthn enrollment stage
# inline — first login without a passkey becomes "enroll one
# now, then validate it". Still passkey-only: there is no way
# through this stage without a WebAuthn credential.
not_configured_action: configure
configuration_stages:
- !Find [authentik_stages_authenticator_webauthn.authenticatorwebauthnstage, [name, default-authenticator-webauthn-setup]]
# Skip re-validation if a passkey was already presented within
# the window. Non-zero for two reasons: (1) no double-touch
# when the login itself used a passkey seconds earlier;
# (2) NetBird's dashboard silently re-authorizes via
# /silent-auth (prompt=none) on token expiry — a flow that
# always demands interaction would fail those and force a
# visible re-login every access-token lifetime.
last_auth_threshold: hours=8
webauthn_user_verification: preferred
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf passkey-stepup-flow
stage: !KeyOf passkey-stepup-webauthn-validation
order: 10
state: present
# Passwordless authentication flow: identification (username or
# email, NO password stage) → WebAuthn validation → login.
- id: passkey-passwordless-flow
model: authentik_flows.flow
identifiers:
slug: passkey-passwordless-authentication
state: present
attrs:
name: Passkey passwordless authentication
title: "Sign in with your passkey"
designation: authentication
- id: passkey-passwordless-identification
model: authentik_stages_identification.identificationstage
identifiers:
name: passkey-passwordless-identification
state: present
attrs:
user_fields:
- username
- email
# SEPARATE stage from the step-up one, and deliberately `deny`:
# at this point in an authentication flow the user has only been
# *identified* (typed a username), not authenticated — `configure`
# here would let anyone who knows a passkey-less username enroll
# their own passkey and take over that account. Inline enrollment
# for passkey-less users happens exclusively in the authorization
# step-up stage above, where require_authenticated guarantees a
# fully authenticated session. A passkey-less user therefore
# bootstraps via password login on auth.renner.uno, then gets the
# enrollment prompt on first app access.
- id: passkey-passwordless-webauthn-validation
model: authentik_stages_authenticator_validate.authenticatorvalidatestage
identifiers:
name: passkey-passwordless-webauthn-validation
state: present
attrs:
device_classes:
- webauthn
not_configured_action: deny
last_auth_threshold: seconds=0
webauthn_user_verification: preferred
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf passkey-passwordless-flow
stage: !KeyOf passkey-passwordless-identification
order: 10
state: present
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf passkey-passwordless-flow
stage: !KeyOf passkey-passwordless-webauthn-validation
order: 20
state: present
- model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf passkey-passwordless-flow
stage: !Find [authentik_stages_user_login.userloginstage, [name, default-authentication-login]]
order: 30
state: present
# Re-point the (previously UI-managed) OAuth2 providers: fresh
# logins use the passwordless flow, every authorize passes the
# step-up flow. Blueprint apply validates the full serializer, so
# the required fields (invalidation_flow, redirect_uris) mirror
# the live values as of 2026-07-07 — git owns them from here on;
# UI edits to these fields will be reverted on the next apply
# cycle.
- model: authentik_providers_oauth2.oauth2provider
identifiers:
name: Homeassistant
state: present
attrs:
authentication_flow: !KeyOf passkey-passwordless-flow
authorization_flow: !KeyOf passkey-stepup-flow
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: confidential
redirect_uris:
- url: https://home.renner.uno/auth/openid/callback
matching_mode: strict
- url: http://homeassistant.srv.renner.uno:8123/auth/openid/callback
matching_mode: strict
- model: authentik_providers_oauth2.oauth2provider
identifiers:
name: Provider for Netbird
state: present
attrs:
authentication_flow: !KeyOf passkey-passwordless-flow
authorization_flow: !KeyOf passkey-stepup-flow
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: public
redirect_uris:
- url: http://localhost:53000
matching_mode: strict
- url: https://netbird.renner.uno/
matching_mode: strict
- url: https://netbird.renner.uno/auth
matching_mode: strict
- url: https://netbird.renner.uno/silent-auth
matching_mode: strict
- url: https://netbird.renner.uno/peers
matching_mode: strict
- url: 'https://netbird\.renner\.uno(/.*)?'
matching_mode: regex
+4 -4
View File
@@ -160,10 +160,10 @@ spec:
redis:
enabled: false
# Mount the K8s-outpost-integration blueprint so the worker re-points
# every Outpost at the in-cluster Kubernetes service connection on
# boot and drops the legacy Docker connection inherited from the
# Proxmox deployment. See blueprints-configmap.yaml.
# Mount the homelab blueprints (K8s outpost integration, passkey
# step-up authorization) — every key of the ConfigMap is applied by
# the worker on boot and on each apply cycle. See
# blueprints-configmap.yaml.
blueprints:
configMaps:
- authentik-blueprints-k8s-outposts