Skip to content

MFA Setup (TOTP)

Authentik supports TOTP-based multi-factor authentication on all SSO services. MFA enforcement is currently disabled — Google OAuth 2FA covers all users via Google's own 2FA enforcement.

Current Status

MFA enforcement: DISABLED (not_configured_action: skip)

All SSO logins go through Google OAuth, which enforces Google's own 2FA. Adding Authentik-level TOTP on top was deemed redundant for the current setup. The playbook, stages, and flows remain in place and can be re-enabled at any time.

To re-enable enforcement:

ssh ansible@ansible-shared-cwiq-io
ansible-helper
cd authentik
ansible-playbook -i inventory.ini policies/configure-mfa.yml

Services Covered

When enabled, MFA applies to all services using Authentik SSO:

Service Protocol
AWS Identity Center SAML
Shared GitLab OIDC
Dev GitLab OIDC
Vault OIDC
Taiga OIDC
Grafana OIDC
Semaphore OIDC
IcingaWeb2 Proxy
SonarQube Proxy

Service accounts and API tokens bypass authentication flows entirely — MFA does not affect them.

Authentication Flows

MFA is enforced on both login paths:

Username/Password Login (default-authentication-flow)

[Identification] (order 10)  — username/email + "Sign in with Google"
[Password] (order 20)        — password entry
[MFA Validation] (order 30)  — TOTP code prompt
       ↓                       No device? → Auto-enrollment (scan QR, verify code, save recovery codes)
[User Login] (order 100)     — session created

Google OAuth Login (default-source-authentication)

[Google OAuth callback]      — Google handles password + Google 2FA
[MFA Validation] (order -1)  — Authentik TOTP prompt
       ↓                       No device? → Auto-enrollment
[User Login] (order 0)       — session created

Both flows use the same MFA validation stage — settings apply uniformly.

Configuration

Set these variables in group_vars/all.yml before running the playbook:

Variable Default Description
mfa_totp_digits 6 Digits in TOTP code (6 or 8)
mfa_recovery_code_count 10 Recovery codes generated per user
mfa_recovery_code_length 16 Character length per recovery code
mfa_not_configured_action configure Behavior when user has no TOTP device

mfa_not_configured_action Values

Value Behavior When to Use
configure Prompt user to enroll TOTP on next login Initial rollout — recommended
skip MFA optional, users can bypass Temporarily disable enforcement
deny Block login until MFA is configured Strict enforcement after rollout

Managing MFA Enforcement

Enable MFA Enforcement

ansible-playbook -i inventory.ini policies/configure-mfa.yml

This runs with the default mfa_not_configured_action: configure — users without a TOTP device are prompted to enroll on their next login.

Disable MFA Enforcement

Disabling MFA requires explicit confirmation

This prevents accidental changes.

ansible-playbook -i inventory.ini policies/configure-mfa.yml \
  -e "mfa_not_configured_action=skip" \
  -e "confirm_mfa_disable=yes_i_understand_the_risk"

Strict Mode — Block Users Without MFA

ansible-playbook -i inventory.ini policies/configure-mfa.yml \
  -e "mfa_not_configured_action=deny"

User Experience

First Login (No TOTP Device)

  1. User logs in (username/password or Google OAuth)
  2. Authentik detects no TOTP device → shows enrollment screen
  3. User scans QR code with authenticator app (Google Authenticator, Authy, 1Password)
  4. User enters the 6-digit code to verify
  5. Authentik displays recovery codes — user saves them securely
  6. Login completes

Subsequent Logins

  1. User logs in normally
  2. Enters 6-digit TOTP code from their authenticator app
  3. Login completes

Recovery Codes

Each recovery code is single-use. If a user loses their authenticator device, they can use a recovery code to log in, then enroll a new device at /if/user/#/settings.

Recovery Procedures

User Lost Their Authenticator Device

If the user has recovery codes:

  1. Log in with a recovery code instead of a TOTP code
  2. Enroll a new TOTP device at https://sso.shared.cwiq.io/if/user/#/settings

If the user has no recovery codes:

  1. Admin: Admin UI > Directory > Users > [user] > MFA Devices
  2. Delete the user's TOTP device
  3. User re-enrolls on next login (if not_configured_action is configure)

Admin Locked Out

The akadmin account behaves like any other user — it will be prompted to enroll if not_configured_action is configure.

If no admin can access the UI at all:

Emergency only — disables MFA for ALL users

A second team member must be notified before proceeding. Document the timestamp and reason. Re-enable MFA immediately after resolving the issue.

ssh cwiq@cwiq-shared-authentik-1
docker exec -it authentik-server ak shell
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
stage = AuthenticatorValidateStage.objects.filter(
    flow__slug="default-authentication-flow"
).first()
if stage:
    stage.not_configured_action = "skip"
    stage.save()
    print("MFA enforcement disabled — RE-ENABLE IMMEDIATELY")

After resolving, re-run policies/configure-mfa.yml and review Authentik event logs for any logins during the bypass window.

Rollout Strategy

  1. Pre-provision admin MFA — Manually configure TOTP for all admin/privileged accounts via the Admin UI before running the playbook. This eliminates the enrollment window for high-privilege accounts.
  2. Pre-test — Have one admin verify their TOTP works across different SSO services (AWS, GitLab) to confirm HA session handling across both instances.
  3. Enable — Run the playbook with default configure action.
  4. Monitor — Watch Authentik event logs for enrollment events over 1–2 weeks.
  5. Enforce (optional) — Change to deny mode to block unenrolled users.

Idempotency

The playbook is fully idempotent: - TOTP and recovery code stages are only created if they don't already exist - The validation stage is updated via PATCH if it already exists - Running the playbook multiple times is safe