feat: Add automated 2FA/MFA enforcement for Authentik

Implements automatic configuration of 2FA enforcement via Authentik API:

**Features:**
- Forces users to configure TOTP authenticator on first login
- Supports multiple 2FA methods: TOTP, WebAuthn, Static backup codes
- Idempotent: detects existing configuration and skips update
- Fully automated via Ansible deployment

**Implementation:**
- New task file: ansible/roles/authentik/tasks/mfa.yml
- Updates default-authentication-mfa-validation stage via API
- Sets not_configured_action to "configure"
- Links default-authenticator-totp-setup as configuration stage

**Configuration:**
```yaml
not_configured_action: configure
device_classes: [totp, webauthn, static]
configuration_stages: [default-authenticator-totp-setup]
```

**Testing:**
 Deployed to dev server successfully
 MFA enforcement verified via API
 Status: "Already configured" (idempotent check works)

Users will now be required to set up 2FA on their next login.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pieter 2026-01-14 16:11:08 +01:00
parent 9571782382
commit 5108662a30
2 changed files with 102 additions and 0 deletions

View file

@ -21,3 +21,8 @@
include_tasks: flows.yml
when: authentik_bootstrap | default(true)
tags: ['authentik', 'flows']
- name: Include MFA/2FA enforcement configuration
include_tasks: mfa.yml
when: authentik_bootstrap | default(true)
tags: ['authentik', 'mfa', '2fa']

View file

@ -0,0 +1,97 @@
---
# Configure 2FA/MFA enforcement in Authentik
- name: Use bootstrap token for API access
set_fact:
authentik_api_token: "{{ client_secrets.authentik_bootstrap_token }}"
- name: Get TOTP setup stage UUID
shell: |
docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \
'http://localhost:9000/api/v3/stages/authenticator/totp/?name=default-authenticator-totp-setup'
register: totp_stage_result
changed_when: false
- name: Parse TOTP stage UUID
set_fact:
totp_stage_pk: "{{ (totp_stage_result.stdout | from_json).results[0].pk }}"
- name: Get current MFA validation stage configuration
shell: |
docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \
'http://localhost:9000/api/v3/stages/authenticator/validate/?name=default-authentication-mfa-validation'
register: mfa_stage_result
changed_when: false
- name: Parse MFA validation stage
set_fact:
mfa_stage: "{{ (mfa_stage_result.stdout | from_json).results[0] }}"
- name: Check if MFA enforcement needs configuration
set_fact:
mfa_needs_update: "{{ mfa_stage.not_configured_action != 'configure' or totp_stage_pk not in (mfa_stage.configuration_stages | default([])) }}"
- name: Create Python script for MFA enforcement
copy:
content: |
import sys, json, urllib.request
base_url = "http://localhost:9000"
token = "{{ authentik_api_token }}"
stage_pk = "{{ mfa_stage.pk }}"
totp_stage_pk = "{{ totp_stage_pk }}"
# Prepare the update payload
payload = {
"name": "{{ mfa_stage.name }}",
"not_configured_action": "configure",
"device_classes": ["totp", "webauthn", "static"],
"configuration_stages": [totp_stage_pk]
}
# Make PATCH request to update the stage
url = f"{base_url}/api/v3/stages/authenticator/validate/{stage_pk}/"
data = json.dumps(payload).encode()
req = urllib.request.Request(url, data=data, method='PATCH')
req.add_header('Authorization', f'Bearer {token}')
req.add_header('Content-Type', 'application/json')
try:
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
print(json.dumps({"success": True, "message": "MFA enforcement configured", "not_configured_action": result.get("not_configured_action")}))
except urllib.error.HTTPError as e:
error_data = e.read().decode()
print(json.dumps({"success": False, "error": error_data}), file=sys.stderr)
sys.exit(1)
dest: /tmp/configure_mfa.py
mode: '0755'
when: mfa_needs_update
- name: Configure MFA enforcement via API
shell: docker exec -i authentik-server python3 < /tmp/configure_mfa.py
register: mfa_config_result
when: mfa_needs_update
- name: Cleanup MFA script
file:
path: /tmp/configure_mfa.py
state: absent
when: mfa_needs_update
- name: Display MFA configuration status
debug:
msg: |
========================================
Authentik 2FA/MFA Enforcement
========================================
Status: {% if mfa_needs_update %}✓ Configured{% else %}✓ Already configured{% endif %}
Configuration:
- Not configured action: Force user to configure authenticator
- Supported methods: TOTP, WebAuthn, Static backup codes
- Configuration stage: default-authenticator-totp-setup
Users will be required to set up 2FA on their next login.
========================================