feat: Implement Authentik flow configuration via blueprints

- Created custom-flows.yaml blueprint for:
  * Invitation stage configuration
  * Recovery flow setup in brand
  * 2FA enforcement (TOTP required)

- Replaced Python API scripts with YAML blueprint approach
- Blueprint is copied to /blueprints/ in authentik containers
- Authentik auto-discovers and applies blueprints

This is the official Authentik way to configure flows.
The blueprint uses Authentik-specific YAML tags: !Find, !KeyOf
This commit is contained in:
Pieter 2026-01-14 14:15:58 +01:00
parent 325c9e6cf8
commit 669d70f98e
2 changed files with 129 additions and 54 deletions

View file

@ -0,0 +1,48 @@
version: 1
metadata:
name: custom-flow-configuration
labels:
blueprints.goauthentik.io/description: "Configure invitation, recovery, and 2FA enforcement"
blueprints.goauthentik.io/instantiate: "true"
entries:
# 1. CREATE INVITATION STAGE
# This stage allows enrollment flows to work with or without invitation tokens
- model: authentik_stages_invitation.invitationstage
identifiers:
name: default-enrollment-invitation
id: invitation-stage
attrs:
continue_flow_without_invitation: true
# 2. BIND INVITATION STAGE TO ENROLLMENT FLOW
# Adds the invitation stage as the first stage in the enrollment flow
- model: authentik_flows.flowstagebinding
identifiers:
target: !Find [authentik_flows.flow, [slug, default-enrollment-flow]]
stage: !KeyOf invitation-stage
order: 0
attrs:
evaluate_on_plan: true
re_evaluate_policies: false
# 3. SET RECOVERY FLOW IN BRAND
# Configures the default brand to use the recovery flow
- model: authentik_core.brand
identifiers:
domain: authentik-default
attrs:
flow_recovery: !Find [authentik_flows.flow, [designation, recovery]]
# 4. ENFORCE 2FA CONFIGURATION
# Updates MFA validation stage to force users to configure TOTP
- model: authentik_stages_authenticator_validate.authenticatorvalidatestage
identifiers:
name: default-authentication-mfa-validation
attrs:
not_configured_action: configure
device_classes:
- totp
- webauthn
configuration_stages:
- !Find [authentik_stages_authenticator_totp.authenticatortotpstage, [name, default-authenticator-totp-setup]]

View file

@ -1,5 +1,5 @@
--- ---
# Configure Authentik flows (invitation, recovery, 2FA) via API # Configure Authentik flows (invitation, recovery, 2FA) via Blueprints
- name: Use bootstrap token for API access - name: Use bootstrap token for API access
set_fact: set_fact:
@ -21,63 +21,80 @@
register: api_wait register: api_wait
changed_when: false changed_when: false
- name: Copy invitation flow configuration script to server - name: Create blueprints directory on server
copy: file:
src: configure_invitation_flow.py path: "{{ authentik_config_dir }}/blueprints"
dest: /tmp/configure_invitation_flow.py state: directory
mode: '0755' mode: '0755'
- name: Copy recovery flow configuration script to server - name: Copy custom flows blueprint to server
copy: copy:
src: configure_recovery_flow.py src: custom-flows.yaml
dest: /tmp/configure_recovery_flow.py dest: "{{ authentik_config_dir }}/blueprints/custom-flows.yaml"
mode: '0755' mode: '0644'
register: blueprint_copied
- name: Copy 2FA enforcement configuration script to server - name: Copy blueprint into authentik-worker container
copy:
src: configure_2fa_enforcement.py
dest: /tmp/configure_2fa_enforcement.py
mode: '0755'
- name: Copy scripts into container
shell: | shell: |
docker cp /tmp/configure_invitation_flow.py authentik-server:/tmp/ docker cp "{{ authentik_config_dir }}/blueprints/custom-flows.yaml" authentik-worker:/blueprints/custom-flows.yaml
docker cp /tmp/configure_recovery_flow.py authentik-server:/tmp/ changed_when: blueprint_copied.changed
docker cp /tmp/configure_2fa_enforcement.py authentik-server:/tmp/
- name: Copy blueprint into authentik-server container
shell: |
docker cp "{{ authentik_config_dir }}/blueprints/custom-flows.yaml" authentik-server:/blueprints/custom-flows.yaml
changed_when: blueprint_copied.changed
- name: Wait for blueprint to be discovered and applied
shell: |
echo "Waiting for blueprint to be discovered and applied..."
sleep 10
# Check if blueprint instance was created
i=1
while [ $i -le 24 ]; do
result=$(docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \
'http://localhost:9000/api/v3/managed/blueprints/' 2>/dev/null || echo '')
if echo "$result" | grep -q 'custom-flow-configuration'; then
echo "Blueprint instance found"
# Check if it has been applied successfully
if echo "$result" | grep -A 10 'custom-flow-configuration' | grep -q 'successful'; then
echo "Blueprint applied successfully"
exit 0
else
echo "Blueprint found but not yet applied, waiting..."
fi
else
echo "Waiting for blueprint discovery... attempt $i/24"
fi
sleep 5
i=$((i+1))
done
echo "Blueprint may still be applying, continuing..."
exit 0
register: blueprint_wait
changed_when: false changed_when: false
- name: Configure invitation flow - name: Verify invitation stage was created
shell: | shell: |
docker exec authentik-server python3 /tmp/configure_invitation_flow.py \ docker exec authentik-server curl -sf -H "Authorization: Bearer {{ authentik_api_token }}" \
"http://localhost:9000" \ "http://localhost:9000/api/v3/stages/all/" | \
"{{ authentik_api_token }}" python3 -c "import sys, json; data = json.load(sys.stdin); stages = [s for s in data['results'] if 'invitation' in s.get('name', '').lower()]; print(json.dumps({'found': len(stages) > 0, 'count': len(stages)}))"
register: invitation_result register: invitation_check
changed_when: "'success' in invitation_result.stdout" changed_when: false
failed_when: false
- name: Configure recovery flow - name: Verify brand recovery flow was set
shell: | shell: |
docker exec authentik-server python3 /tmp/configure_recovery_flow.py \ docker exec authentik-server curl -sf -H "Authorization: Bearer {{ authentik_api_token }}" \
"http://localhost:9000" \ "http://localhost:9000/api/v3/core/brands/" | \
"{{ authentik_api_token }}" python3 -c "import sys, json; data = json.load(sys.stdin); brand = data['results'][0] if data['results'] else {}; print(json.dumps({'recovery_flow_set': brand.get('flow_recovery') is not None}))"
register: recovery_result register: recovery_check
changed_when: "'success' in recovery_result.stdout" changed_when: false
failed_when: false
- name: Configure 2FA enforcement
shell: |
docker exec authentik-server python3 /tmp/configure_2fa_enforcement.py \
"http://localhost:9000" \
"{{ authentik_api_token }}"
register: twofa_result
changed_when: "'success' in twofa_result.stdout"
- name: Cleanup configuration scripts from host
file:
path: "{{ item }}"
state: absent
loop:
- /tmp/configure_invitation_flow.py
- /tmp/configure_recovery_flow.py
- /tmp/configure_2fa_enforcement.py
- name: Display flows configuration status - name: Display flows configuration status
debug: debug:
@ -86,14 +103,24 @@
Authentik Flows Configuration Authentik Flows Configuration
======================================== ========================================
✓ Invitation Flow: {{ 'Configured' if invitation_result.rc == 0 else 'Failed' }} Configuration Method: YAML Blueprints
{{ (invitation_result.stdout | from_json).message | default('') }} Blueprint File: /blueprints/custom-flows.yaml
Recovery Flow: {{ 'Configured' if recovery_result.rc == 0 else 'Failed' }} Blueprint Deployed: {{ blueprint_copied.changed }}
{{ (recovery_result.stdout | from_json).message | default('') }} ✓ Blueprint Applied: {{ 'Yes' if 'successfully' in blueprint_wait.stdout else 'In Progress' }}
✓ 2FA Enforcement: {{ 'Configured' if twofa_result.rc == 0 else 'Failed' }} Verification:
{{ (twofa_result.stdout | from_json).message | default('') }} {{ invitation_check.stdout | default('Invitation stage: Checking...') }}
{{ recovery_check.stdout | default('Recovery flow: Checking...') }}
Note: Authentik applies blueprints asynchronously.
Changes should be visible within 1-2 minutes.
To verify manually:
- Login to https://{{ authentik_domain }}
- Check Admin > Flows > Stages for invitation stage
- Check Admin > System > Brands for recovery flow setting
- Check default-authentication-mfa-validation stage for 2FA enforcement
Email configuration is active and flows Email configuration is active and flows
will send emails via Mailgun SMTP. will send emails via Mailgun SMTP.