27 KiB
Authentik User Management: Complete Roundup & Operations Guide
This guide covers everything you need to know about managing users in Authentik for the Haumdaucher infrastructure (https://auth.haumdaucher.de): from manual creation and invitation workflows to Terraform IaC, custom email branding, fine-grained access control, handling custom attributes (e.g., Vereins-Mitgliedsnummer, second email), and bulk-importing users from an Excel / CSV spreadsheet.
📑 Table of Contents
- Executive Summary: Quick Answers
- Method 1: Manual Web UI & Invitation Links
- Method 2: User Provisioning via Terraform
- Method 3: Bulk Import from Excel / CSV (Python Script)
- Customizing Registration & Onboarding Emails
- Access Control & Authorization Policies (Limiting App Access)
- Custom Attributes & OIDC Claim Mappings
- Production Checklist & Best Practices
⚡ Executive Summary: Quick Answers
| Question | Answer & Key Mechanism |
|---|---|
| How to add new users? | 4 Ways: (1) Admin Web UI (Directory > Users), (2) Invitations flow (Directory > Invitations - recommended for self-service), (3) Terraform (authentik_user resource), or (4) REST API / Python script for bulk imports. |
| Can this be done in Terraform? | Yes. Use resource "authentik_user" in terraform/authentik.tf. Supports custom attributes via jsonencode(), group assignments, and active status. |
| Can I change the email sent to the user? | Yes. Configured in Flows & Stages > Stages > Email Stage. You can customize the subject line, token expiration, and HTML template (written in Django template syntax with access to user fields and custom attributes). |
| Can I limit what a user has access to? | Yes. Through Policy Bindings attached to Applications: Group Membership Policies (e.g., infra-admins vs users), IP/Geo Policies, or Python Expression Policies checking custom attributes (e.g., only active club members). |
| How to handle Excel tables with custom attributes? | Authentik stores arbitrary key-value pairs in user.attributes (JSON). You can import an Excel/CSV file with a Python script or Terraform csvdecode(), map fields like haumdaucher_mitgliedsnummer or second_email, and expose them to downstream apps (Nextcloud, Grafana, Gitea) via OIDC Scope Mappings. |
👤 Method 1: Manual Web UI & Invitation Links
Authentik offers two UI methods to onboard users:
Option A: Direct User Creation (Admin sets password or triggers reset)
- Go to
https://auth.haumdaucher.deand log in with your admin account. - Navigate to Directory > Users and click Create.
- Fill in:
- Username:
max.mustermann - Name:
Max Mustermann - Email:
max@example.com - Type:
Internal - Groups: Select groups (e.g.,
users,verein-mitglieder). - Attributes (YAML/JSON):
{ "second_email": "max.privat@gmail.com", "haumdaucher_mitgliedsnummer": "HM-2024-042", "phone": "+49 170 1234567" }
- Username:
- Click Create.
- To set credentials: Click on the user -> Actions -> Set Password (or Explicitly trigger password reset flow so the user receives a link via email).
⚠️ Troubleshooting: "Flow does not apply to current user"
If you encounter this screen when opening an invite link:
"Flow does not apply to current user."
Why this happens:
- Wrong Flow Selected (
default-source-enrollment): In your URL (/if/flow/default-source-enrollment/?itoken=...), the flow used wasdefault-source-enrollment. That flow is strictly designed for external identity providers (OAuth/SAML like Google/GitHub) and binds a policy that checks for an active external source in the context. When accessed directly with an?itoken=..., Authentik evaluates this policy, fails, and displays this error. - Missing Invitation Stage:
Standard enrollment flows reject direct token access unless an
Invitation Stageis bound to the beginning of the flow to validate?itoken=.... - Active Admin Session: If an administrator is currently logged in to the browser and clicks the enrollment link, flows with an "unauthenticated-only" policy will reject the session. Always test invitation URLs in an Incognito / Private window.
Solution: The Haumdaucher Custom Invitation Flow (haumdaucher-invitation-flow)
We provision a dedicated, streamlined flow in terraform/authentik.tf:
graph LR
Link[Invite Link with ?itoken=...] --> Stage1[1. Invitation Stage<br/>Validates Token]
Stage1 --> Stage2[2. Prompt Stage<br/>User inputs Username, Name, Password]
Stage2 --> Stage3[3. User Write Stage<br/>Writes to DB & assigns Groups]
Stage3 --> Stage4[4. User Login Stage<br/>Logs user into session]
Stage4 --> Stage5[5. Redirect Stage<br/>Redirects to haumdaucher.de]
Flow Behavior (Tailored to your setup):
- User Inputs: Prompts the user to choose their preferred Username, Full Name, and Password (with confirmation).
- Email & Custom Attributes: Pre-filled from the admin invitation (e.g.
haumdaucher_mitgliedsnummer,second_email). - MFA / 2FA: Optional. The user registers immediately with their password and can enroll Passkeys/TOTP anytime in their profile settings.
- Automatic Redirect: Upon completing registration, the user is automatically logged in and redirected to
https://haumdaucher.de.
How to Create an Invitation using the Custom Flow
- Go to
https://auth.haumdaucher.de> Directory > Invitations. - Click Create (or New Invitation).
- Set the fields:
- Name:
Invite - Max Mustermann - Expires: Set expiration (e.g., 7 days).
- Flow: Select
Haumdaucher Invitation Flow(haumdaucher-invitation-flow). - Single use: Checked (
true). - Fixed Data / Attributes (JSON):
{ "email": "max@example.com", "haumdaucher_mitgliedsnummer": "HM-2024-042", "second_email": "max.privat@gmail.com" }
- Name:
- Click Create.
- Copy the generated URL:
https://auth.haumdaucher.de/if/flow/haumdaucher-invitation-flow/?itoken=<generated-token> - Send the link to the user. When they open it, they will see the branded Haumdaucher registration form asking for their Username, Name, and Password!
🏗️ Method 2: User Provisioning via Terraform
You can manage users, groups, and permissions directly in terraform/authentik.tf.
1. Single User Definition
# Group for club members
resource "authentik_group" "verein_mitglieder" {
name = "verein-mitglieder"
is_superuser = false
}
# User with custom attributes
resource "authentik_user" "max_mustermann" {
username = "max.mustermann"
name = "Max Mustermann"
email = "max@example.com"
type = "internal"
is_active = true
groups = [
authentik_group.users.id,
authentik_group.verein_mitglieder.id
]
attributes = jsonencode({
"second_email" = "max.privat@gmail.com",
"haumdaucher_mitgliedsnummer" = "HM-2024-042",
"eintrittsdatum" = "2024-01-15",
"status" = "aktiv"
})
}
2. Batch Provisioning from CSV in Terraform
If you maintain users in a CSV file (terraform/users.csv):
username,name,email,second_email,mitgliedsnummer,is_admin
max.mustermann,Max Mustermann,max@example.com,max.privat@gmail.com,HM-2024-042,false
anna.schmidt,Anna Schmidt,anna@example.com,anna.work@company.de,HM-2024-043,true
In terraform/authentik.tf:
locals {
csv_users = csvdecode(file("${path.module}/users.csv"))
}
resource "authentik_user" "csv_members" {
for_each = { for u in local.csv_users : u.username => u }
username = each.value.username
name = each.value.name
email = each.value.email
type = "internal"
is_active = true
groups = compact([
authentik_group.users.id,
authentik_group.verein_mitglieder.id,
each.value.is_admin == "true" ? authentik_group.infra_admins.id : null
])
attributes = jsonencode({
"second_email" = each.value.second_email,
"haumdaucher_mitgliedsnummer" = each.value.mitgliedsnummer
})
}
⚖️ Trade-offs: When to use Terraform for Users
- Pros: Version-controlled in Git, declarative, reproducible, ideal for admins and service accounts (
ai_assistant). - Cons / Considerations: Terraform cannot trigger interactive password onboarding emails out of the box; passwords must be set via recovery flow or first login reset.
📊 Method 3: Bulk Import from Excel / CSV (Python Script)
If you have an Excel file (mitglieder.xlsx) or CSV, using Authentik's REST API is the cleanest approach because it can create users, attach custom attributes, and automatically email them an invitation link to set their own password.
Ready-to-Run Python Import Script
Save this script (e.g. k8s/auth/scripts/import_users_excel.py):
#!/usr/bin/env python3
"""
Bulk User Importer for Authentik (auth.haumdaucher.de)
Reads an Excel (.xlsx) or CSV file and creates users with custom attributes and invitation links.
"""
import sys
import os
import requests
import pandas as pd
# Configuration
AUTHENTIK_URL = os.getenv("AUTHENTIK_URL", "https://auth.haumdaucher.de")
AUTHENTIK_TOKEN = os.getenv("AUTHENTIK_TOKEN") # API Token (e.g. ai_assistant_token)
if not AUTHENTIK_TOKEN:
print("❌ Error: AUTHENTIK_TOKEN environment variable is required.")
sys.exit(1)
HEADERS = {
"Authorization": f"Bearer {AUTHENTIK_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def get_or_create_group(group_name: str) -> str:
"""Fetch group ID or create if it doesn't exist."""
res = requests.get(f"{AUTHENTIK_URL}/api/v3/core/groups/?name={group_name}", headers=HEADERS)
res.raise_for_status()
results = res.json().get("results", [])
if results:
return results[0]["pk"]
# Create group
create_res = requests.post(f"{AUTHENTIK_URL}/api/v3/core/groups/", headers=HEADERS, json={"name": group_name})
create_res.raise_for_status()
return create_res.json()["pk"]
def import_users_from_file(file_path: str, send_invitation: bool = True):
# Read Excel or CSV
if file_path.endswith((".xlsx", ".xls")):
df = pd.read_excel(file_path)
else:
df = pd.read_csv(file_path)
# Standardize column names (lowercase & stripped)
df.columns = [c.strip().lower() for c in df.columns]
print(f"📋 Found {len(df)} rows to process.")
users_group_pk = get_or_create_group("users")
verein_group_pk = get_or_create_group("verein-mitglieder")
for idx, row in df.iterrows():
name = str(row.get("name", "")).strip()
email = str(row.get("email", "")).strip()
username = str(row.get("username", "")).strip() or email.split("@")[0].lower()
second_email = str(row.get("second_email", row.get("zweite_email", ""))).strip()
mitgliedsnummer = str(row.get("mitgliedsnummer", row.get("verein_mitgliedsnummer", ""))).strip()
if not email or email == "nan":
print(f"⚠️ Skipping row {idx+1}: missing email")
continue
# Prepare user payload with custom attributes
user_payload = {
"username": username,
"name": name if name != "nan" else username,
"email": email,
"type": "internal",
"is_active": True,
"groups": [users_group_pk, verein_group_pk],
"attributes": {
"second_email": second_email if second_email != "nan" else "",
"haumdaucher_mitgliedsnummer": mitgliedsnummer if mitgliedsnummer != "nan" else "",
"source": "excel_import"
}
}
# 1. Create or Update User in Authentik
res = requests.get(f"{AUTHENTIK_URL}/api/v3/core/users/?username={username}", headers=HEADERS)
res.raise_for_status()
existing = res.json().get("results", [])
if existing:
user_pk = existing[0]["pk"]
patch_res = requests.patch(f"{AUTHENTIK_URL}/api/v3/core/users/{user_pk}/", headers=HEADERS, json=user_payload)
patch_res.raise_for_status()
print(f"🔄 Updated existing user: {username} ({email})")
else:
create_res = requests.post(f"{AUTHENTIK_URL}/api/v3/core/users/", headers=HEADERS, json=user_payload)
if not create_res.ok:
print(f"❌ Failed to create {username}: {create_res.text}")
continue
user_pk = create_res.json()["pk"]
print(f"✅ Created user: {username} ({email}) [ID: {user_pk}]")
# 2. Generate Invitation / Password Reset Link
if send_invitation:
invitation_payload = {
"name": f"Onboarding {username}",
"single_use": True,
"fixed_data": {
"email": email,
"username": username
}
}
inv_res = requests.post(f"{AUTHENTIK_URL}/api/v3/stages/invitation/invitations/", headers=HEADERS, json=invitation_payload)
if inv_res.ok:
inv_key = inv_res.json()["pk"]
link = f"{AUTHENTIK_URL}/if/flow/default-enrollment-flow/?itoken={inv_key}"
print(f" ✉️ Invitation Link: {link}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 import_users_excel.py <path-to-excel-or-csv>")
sys.exit(1)
import_users_from_file(sys.argv[1])
✉️ Customizing Registration & Onboarding Emails
When an admin creates a user or triggers an enrollment/password recovery, Authentik sends transactional emails. You can customize their sender address, subject line, and full HTML appearance.
1. SMTP & Sender Settings
Authentik sends emails using the SMTP configuration defined in k8s/auth/authentik.secret.values.yaml or global environment variables:
AUTHENTIK_EMAIL__HOST:smtp.gmail.com(or Google Workspace / OVH relay)AUTHENTIK_EMAIL__PORT:587AUTHENTIK_EMAIL__USE_TLS:trueAUTHENTIK_EMAIL__USERNAME:service@haumdaucher.deAUTHENTIK_EMAIL__PASSWORD:<app-password>AUTHENTIK_EMAIL__FROM:HAUMDAUCHER Identity <service@haumdaucher.de>
2. Customizing Email Stages in Authentik
Authentik controls outgoing emails via Email Stages:
- Open Admin UI: Flows & Stages > Stages.
- Locate the relevant email stage:
default-password-reset-email(for password recovery & manual user activation)default-enrollment-email/invitation-email(for new user invitations)default-user-settings-password-reset-email
- Click Edit on the stage:
- Subject:
Willkommen bei Haumdaucher e.V. – Aktiviere deinen Zugang - Token Expiry:
Minutes=1440(24 hours) orMinutes=10080(7 days) - Template: Select standard template or your custom template path.
- Subject:
3. Customizing the HTML Email Template (Django Syntax)
Authentik email templates use Django template syntax. Available variables in email templates include:
{{ user.name }}/{{ user.username }}/{{ user.email }}{{ user.attributes.haumdaucher_mitgliedsnummer }}{{ url }}(The activation / password reset link){{ token_expires }}(Expiry timestamp){{ brand.branding_title }}(HAUMDAUCHER)
Example Custom Haumdaucher Welcome Template
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #0d0d12; color: #f0f0f5; margin: 0; padding: 40px; }
.card { background: #181824; border: 1px solid rgba(212, 175, 55, 0.3); border-radius: 16px; max-width: 540px; margin: 0 auto; padding: 32px; box-shadow: 0 12px 30px rgba(0,0,0,0.5); }
.header { text-align: center; margin-bottom: 24px; }
.logo { height: 60px; }
h1 { color: #d4af37; font-size: 22px; margin-top: 12px; }
p { color: #c0c0d0; line-height: 1.6; font-size: 15px; }
.btn { display: block; text-align: center; background: linear-gradient(135deg, #d4af37, #aa8520); color: #000; font-weight: bold; text-decoration: none; padding: 14px 24px; border-radius: 10px; margin: 28px 0; }
.meta { background: rgba(255,255,255,0.05); border-radius: 8px; padding: 12px 16px; font-size: 13px; color: #a0a0b0; }
.footer { text-align: center; font-size: 12px; color: #606070; margin-top: 30px; }
</style>
</head>
<body>
<div class="card">
<div class="header">
<img class="logo" src="https://haumdaucher.de/images/logo_classic.png" alt="Haumdaucher Logo" />
<h1>Willkommen beim Haumdaucher e.V.</h1>
</div>
<p>Hallo <strong>{{ user.name }}</strong>,</p>
<p>dein zentraler Zugang für die Haumdaucher Cloud & Services wurde eingerichtet.</p>
{% if user.attributes.haumdaucher_mitgliedsnummer %}
<div class="meta">
<strong>Mitgliedsnummer:</strong> {{ user.attributes.haumdaucher_mitgliedsnummer }}<br/>
<strong>Benutzername:</strong> {{ user.username }}
</div>
{% endif %}
<p>Klicke auf den Button unten, um dein Passwort festzulegen und deinen Zugang zu aktivieren:</p>
<a class="btn" href="{{ url }}">Zugang aktivieren & Passwort setzen</a>
<p style="font-size: 12px; color: #888;">Dieser Link ist 7 Tage lang gültig. Falls du die Registrierung nicht angefordert hast, kannst du diese E-Mail ignorieren.</p>
<div class="footer">
HAUMDAUCHER e.V. • <a href="https://haumdaucher.de" style="color: #d4af37;">haumdaucher.de</a>
</div>
</div>
</body>
</html>
🔒 Access Control & Authorization Policies (Limiting App Access)
By default, an application without an attached policy allows any authenticated user to access it. Authentik provides fine-grained Access Control (RBAC & PBAC) to restrict access per application.
graph TD
subgraph "Authentik Directory Groups"
Admin["👑 <b>infra-admins</b><br/>(Moritz & Wife / Full Admin)"]
Verein["🍺 <b>verein-mitglieder</b><br/>(Club Members)"]
Normal["👤 <b>users</b><br/>(Normal Authenticated Users)"]
end
subgraph "Tier 1: Admin & Ops Tools (ForwardAuth / OIDC)"
Z2M["Zigbee2MQTT"]
Prom["Prometheus & Alertmanager"]
PMA["phpMyAdmin (Nextcloud & Gitea)"]
N8N["n8n Automation"]
MinIO["MinIO Console"]
Coder["Coder v2"]
OpenClaw["OpenClaw Engine"]
end
subgraph "Tier 2: Daily Shared Services (OIDC)"
HASS["Home Assistant"]
Grafana["Grafana Dashboards"]
Nextcloud["Nextcloud Cloud"]
Gitea["Gitea Git Platform"]
ClubWeb["haumdaucher.de"]
end
%% Admin Links (Full Superadmin access everywhere)
Admin ==>|Full Access| Z2M
Admin ==>|Full Access| OpenClaw
Admin ==>|Admin| HASS
Admin ==>|Admin| Grafana
Admin ==>|Admin| Nextcloud
Admin ==>|Admin| Gitea
Admin ==>|Admin| ClubWeb
%% Normal User Links (Restricted)
Normal -->|Viewer (Read-Only)| Grafana
Normal -->|Dashboard View| HASS
Normal -->|Standard User| Nextcloud
Normal -->|Developer| Gitea
%% Club Members Links
Verein -->|Member Portal| ClubWeb
Verein -->|Club Shared Folder| Nextcloud
Verein -->|Viewer (Read-Only)| Grafana
Master Permission Matrix
| Application | Domain | Auth Type | infra-admins (Moritz & Wife) |
verein-mitglieder (Club) |
users (Normal Users) |
|---|---|---|---|---|---|
| OpenClaw | openclaw.haumdaucher.de |
ForwardAuth | Full Access ✅ | ❌ Denied | ❌ Denied |
| Zigbee2MQTT | zigbee2mqtt.moritzgraf.de |
ForwardAuth | Full Access ✅ | ❌ Denied | ❌ Denied |
| Prometheus / Alertmanager | prometheus.haumdaucher.de |
ForwardAuth | Full Access ✅ | ❌ Denied | ❌ Denied |
| phpMyAdmin (All DBs) | *.phpmyadmin.haumdaucher.de |
ForwardAuth | Full Access ✅ | ❌ Denied | ❌ Denied |
| n8n | n8n.moritzgraf.de |
ForwardAuth | Full Access ✅ | ❌ Denied | ❌ Denied |
| MinIO Console | minio-console.haumdaucher.de |
Native OIDC | consoleAdmin ✅ |
❌ Denied | ❌ Denied |
| Coder v2 | coder.moritzgraf.de |
Native OIDC | Owner / Admin ✅ |
❌ Denied | ❌ Denied |
| Home Assistant | hass.moritzgraf.de |
Native OIDC | Administrator ✅ |
❌ Denied | Standard User |
| Grafana | grafana.haumdaucher.de |
Native OIDC | Admin ✅ |
Viewer (Read-Only) |
Viewer (Read-Only) |
| Nextcloud | cloud.haumdaucher.de |
Native OIDC | Admin ✅ |
User (Club Folder) |
User (Standard) |
| Gitea | git.moritzgraf.de |
Native OIDC | Admin ✅ |
❌ / Member | Developer |
| haumdaucher.de | haumdaucher.de |
ForwardAuth / OIDC | Admin ✅ |
Member Access ✅ |
❌ Denied |
Policy Bindings in Terraform
All policy bindings are enforced directly in terraform/authentik.tf:
# 1. Admin-Only Tools (Restricted strictly to infra-admins)
resource "authentik_policy_binding" "openclaw_admin_only" {
target = authentik_application.openclaw.uuid
group = authentik_group.infra_admins.id
order = 0
}
resource "authentik_policy_binding" "zigbee2mqtt_admin_only" {
target = authentik_application.zigbee2mqtt.uuid
group = authentik_group.infra_admins.id
order = 0
}
# 2. Shared & Member Applications (Accessible to users group)
resource "authentik_policy_binding" "grafana_users" {
target = authentik_application.grafana.uuid
group = authentik_group.users.id
order = 0
}
resource "authentik_policy_binding" "nextcloud_users" {
target = authentik_application.nextcloud.uuid
group = authentik_group.users.id
order = 0
}
2. Attribute-Based Policy (Python Expression Policy)
You can write Python expressions that evaluate custom user attributes dynamically:
resource "authentik_policy_expression" "require_active_membership" {
name = "require-active-haumdaucher-membership"
expression = <<EOF
# Must have a valid Mitgliedsnummer and active status
mitgliedsnummer = request.user.attributes.get('haumdaucher_mitgliedsnummer')
status = request.user.attributes.get('status', 'aktiv')
if not mitgliedsnummer or not mitgliedsnummer.startswith('HM-'):
ak_message("Keine gültige Haumdaucher Mitgliedsnummer hinterlegt.")
return False
if status != 'aktiv':
ak_message("Deine Mitgliedschaft ist derzeit nicht aktiv.")
return False
return True
EOF
}
# Attach policy to Nextcloud application
resource "authentik_policy_binding" "nextcloud_active_members" {
target = authentik_application.nextcloud.uuid
policy = authentik_policy_expression.require_active_membership.id
order = 1
}
🏷️ Custom Attributes & OIDC Claim Mappings
Authentik allows storing any arbitrary JSON object inside user.attributes.
1. Recommended Attribute Schema for Haumdaucher
{
"haumdaucher_mitgliedsnummer": "HM-2024-042",
"second_email": "max.privat@gmail.com",
"eintrittsdatum": "2024-01-15",
"verein_rolle": "Vorstand / Kassenwart / Mitglied",
"phone": "+49 170 1234567",
"telegram_handle": "@max_mustermann"
}
2. Exposing Custom Attributes via OIDC Claims (Terraform)
To pass haumdaucher_mitgliedsnummer or second_email to downstream applications like Nextcloud, Grafana, or Gitea:
# Define a custom Scope Mapping in Terraform
resource "authentik_property_mapping_provider_scope" "haumdaucher_profile" {
name = "Haumdaucher Extended Profile"
scope_name = "haumdaucher_profile"
description = "Provides Vereins-Mitgliedsnummer and secondary email to OIDC clients"
expression = <<EOF
return {
"haumdaucher_mitgliedsnummer": request.user.attributes.get("haumdaucher_mitgliedsnummer", ""),
"second_email": request.user.attributes.get("second_email", ""),
"verein_rolle": request.user.attributes.get("verein_rolle", "Mitglied")
}
EOF
}
# Add this mapping to the OAuth2 provider (e.g. Nextcloud or Grafana)
resource "authentik_provider_oauth2" "nextcloud" {
name = "Nextcloud"
client_id = "nextcloud"
client_secret = random_password.nextcloud_oauth_client_secret.result
client_type = "confidential"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
signing_key = data.authentik_certificate_key_pair.generated.id
property_mappings = [
data.authentik_property_mapping_provider_scope.openid.id,
data.authentik_property_mapping_provider_scope.email.id,
data.authentik_property_mapping_provider_scope.profile.id,
authentik_property_mapping_provider_scope.haumdaucher_profile.id, # <-- Custom scope
]
}
🛡️ Production Checklist & Best Practices
When managing users in production, keep the following considerations in mind:
1. Passwordless & Multi-Factor Authentication (MFA / 2FA)
- Enforce WebAuthn / Passkeys (FIDO2) or TOTP (Authenticator App) for users in the
infra-adminsgroup. - In Authentik, this is done by binding an Authenticator Validation Stage to the default authentication flow or creating an Admin Authorization Flow.
2. User Self-Service & Attribute Editing
- Authentik provides a self-service portal at
https://auth.haumdaucher.de/if/user/#/settings. - Users can change their password, register Passkeys, and edit user profile fields.
- Security Rule: Keep administrative attributes (like
haumdaucher_mitgliedsnummer) read-only by not exposing them to the user edit flow, while allowing users to freely edit personal contact details likesecond_emailorphone.
3. Offboarding & Member Deactivation
- If a member leaves the club:
- Set
is_active = false(or remove fromverein-mitgliedergroup). - Authentik immediately terminates active sessions and revokes ForwardAuth access and OIDC refresh tokens.
- No need to manually reset passwords on Nextcloud, Grafana, or Gitea—SSO handles deprovisioning instantly.
- Set
4. GDPR / DSGVO & Privacy
- Store only necessary data in user attributes.
- If a member requests account deletion, deleting the user in Authentik cascades identity termination across all connected services.
5. Backups & Audit Logs
- User database changes are stored in the PostgreSQL cluster (
authentik-db) and backed up with daily Velero snapshot schedules. - Authentik logs all login attempts, failed logins, and administrative modifications under Events > Logs.