Skip to content

Authentik Operations

Operational guide for Authentik SSO configuration, email verification, and OIDC troubleshooting.

Quick Reference

Property Value
URL https://auth.fzymgc.house
Admin UI https://auth.fzymgc.house/if/admin/
User Settings https://auth.fzymgc.house/if/user/
API Docs https://api.goauthentik.io
OIDC Issuer (K8s) https://auth.fzymgc.house/application/o/kubernetes/
Kubernetes Client ID kubernetes
Terraform Module tf/authentik/
Vault Secret Path secret/fzymgc-house/cluster/authentik

Architecture Overview

Email Verification Flow

Authentik does NOT have a native email_verified field. The email_verified claim must be stored in user.attributes.email_verified and read via a custom scope mapping.

+----------------------------------------------------------------+
|                    Email Verification Flow                      |
+----------------------------------------------------------------+
|  New User Enrollment:                                           |
|  1. User fills enrollment form (prompt stage)                   |
|  2. User account created inactive (write stage)                 |
|  3. Verification email sent (email stage)                       |
|  4. User clicks link, email verified                            |
|  5. Expression policy sets user.attributes.email_verified=true  |
|  6. User logged in (login stage)                                |
+----------------------------------------------------------------+
|  OIDC Token Generation:                                         |
|  1. User authenticates                                          |
|  2. Custom email scope reads user.attributes.email_verified     |
|  3. JWT includes email_verified claim with actual value         |
+----------------------------------------------------------------+

Key Terraform Resources

Resource File Purpose
authentik_property_mapping_provider_scope.email_verified data-sources.tf Custom email scope with email_verified from attributes
authentik_flow.enrollment_with_email_verification enrollment-flow.tf Enrollment flow with email verification
authentik_stage_email.enrollment_email_verification enrollment-flow.tf Email verification stage
authentik_policy_expression.set_email_verified enrollment-flow.tf Sets email_verified: true after verification

User Management

Create User

  1. Navigate to Admin UI > Directory > Users
  2. Click "Create" and fill in details
  3. Assign to appropriate groups

Disable User

  1. Find user in Admin UI
  2. Set "Is Active" to false
  3. User immediately loses access

Password Reset

Users can self-service via "Forgot Password" flow if email is configured.

Group Management

Access Control Pattern

Applications use a two-tier group model with policy_engine_mode = "any" and dual policy bindings:

Base Group Admin Group Application
vault-users vault-admin Vault
grafana-editor grafana-admin Grafana
temporal-users temporal-admin Temporal
mealie-users mealie-admins Mealie (groups via OIDC claims, no binding)
paperless-users paperless-admin Paperless (no TF application)
uptime-kuma-users Uptime Kuma (launcher only)

Each application has policy bindings for both the base and admin groups. This grants access to admin group members directly, without relying on group parent hierarchy.

Why dual bindings? Authentik 2025.12.x has a known bug (#15159) where the parents M2M field silently fails to persist. Until this is fixed upstream, admin group members would not inherit base group access through hierarchy. The dual binding pattern is safe to keep even after the bug is fixed.

Core Groups

Group Access
authentik Admins Full Authentik admin
cluster-admins Kubernetes admin access
k8s-admins Kubernetes OIDC admin

OIDC Applications

Configured applications using OIDC:

  • Kubernetes API (cluster access)
  • Vault
  • Grafana

Forward Auth Applications

Applications using Traefik forward auth with Authentik proxy providers. These apps lack native OIDC support, so authentication is handled at the reverse proxy layer via Traefik middleware.

  • Temporal (temporal.fzymgc.house) - Managed via tf/authentik/temporal.tf
  • Longhorn (longhorn.fzymgc.house) - Uses embedded outpost (no dedicated provider)

Note: The embedded outpost at /outpost.goauthentik.io/auth/traefik routes requests to the correct application based on the Host header matching the provider's external_host configuration.

Temporal Access Management

Temporal Web UI uses forward auth with group-based access control. Access is managed via the temporal-users and temporal-admin groups.

Groups:

Group Purpose
temporal-users Gate access to Temporal Web UI
temporal-admin Future admin functionality (inherits from temporal-users)

Note: Forward auth only controls access to the UI itself. Both groups currently have identical UI access since Temporal Web UI doesn't have built-in role-based authorization. The admin group is created for future integration with Temporal's namespace-level RBAC if needed.

Grant access:

  1. Navigate to Authentik Admin → Directory → Groups
  2. Select temporal-users (or temporal-admin for designated admins)
  3. Add user to group membership

Revoke access:

Remove user from temporal-users group. Access is revoked on next request (token validity is 24 hours, but the policy check happens on each auth request).

Terraform management:

Users managed in Terraform can be added to groups in tf/authentik/users.tf:

resource "authentik_user" "example" {
  username = "example"
  name     = "Example User"
  email    = "example@domain.com"

  groups = [
    # Choose ONE (admin inherits user access via parent group):
    authentik_group.temporal_users.id,   # Basic access
    # authentik_group.temporal_admin.id, # Admin access (includes user-level)
  ]
}

Common Operations

Setting email_verified for Existing Users

Warning: Direct attribute manipulation bypasses enrollment flows and should only be used for migrating existing users. New users should always go through the enrollment flow.

Existing users created before the email verification flow need manual attribute setting:

# Get Authentik token from Vault
export VAULT_ADDR=https://vault.fzymgc.house
AUTHENTIK_TOKEN=$(vault kv get -field=terraform_token secret/fzymgc-house/cluster/authentik)

# Find user ID
curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  'https://auth.fzymgc.house/api/v3/core/users/?search=USERNAME' | jq '.results[0].pk'

# Set email_verified attribute (replace USER_ID)
curl -s -X PATCH \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"attributes": {"email_verified": true}}' \
  "https://auth.fzymgc.house/api/v3/core/users/USER_ID/"

To force a user to re-consent to an application:

  1. Go to https://auth.fzymgc.house/if/user/#/settings
  2. Navigate to Consent section
  3. Select the application and click Delete

Debugging OIDC Flows

  1. Check provider configuration:

    terraform -chdir=tf/authentik state show authentik_provider_oauth2.kubernetes
    

  2. List scope mappings on provider:

    terraform -chdir=tf/authentik state show authentik_provider_oauth2.kubernetes | grep -A20 property_mappings
    

  3. Verify scope mapping expression:

    terraform -chdir=tf/authentik state show authentik_property_mapping_provider_scope.email_verified
    

Troubleshooting

OIDC Authentication Returns "Unauthorized"

Symptom: kubectl --context fzymgc-house-oidc get nodes returns Unauthorized

Diagnosis Steps:

  1. Clear OIDC cache and re-authenticate:

    rm -rf ~/.kube/cache/oidc-login/
    kubectl --context fzymgc-house-oidc get nodes
    

  2. Decode the JWT to check claims:

    jq -r '.id_token | split(".")[1]' ~/.kube/cache/oidc-login/* | \
      python3 -c "import sys,json,base64; print(json.dumps(json.loads(base64.urlsafe_b64decode(sys.stdin.read().strip()+'==')),indent=2))"
    

  3. Check if email_verified is true in the JWT

Common Causes:

Cause Solution
email_verified: false in JWT User's attributes.email_verified not set - see "Setting email_verified for Existing Users"
Missing groups claim Check user group membership in Authentik
Token expired Clear cache and re-authenticate
SSO session caching old claims Log out of Authentik completely, then re-authenticate

email_verified Always False

Root Cause: Authentik's default email scope returns email_verified: false for security reasons (see GitHub issue #16205).

Solution: Use custom email scope that reads from user.attributes.email_verified:

# Custom scope expression (in data-sources.tf)
return {
    "email": request.user.email,
    "email_verified": request.user.attributes.get("email_verified", False),
}

Symptom: OAuth consent only shows "General Profile Information", not email

Cause: Scope mappings without a description field are treated as "silent" scopes

Solution: Add description to the scope mapping:

resource "authentik_property_mapping_provider_scope" "email_verified" {
  name        = "Email (with verified attribute)"
  scope_name  = "email"
  description = "Email Address"  # Required for consent display
  expression  = "..."
}

Terraform Resets User Attributes

Symptom: email_verified attribute disappears after Terraform apply

Cause: User resources managed by Terraform without ignore_changes for attributes

Solution: Add attributes to lifecycle ignore_changes:

resource "authentik_user" "example" {
  # ... user config ...

  lifecycle {
    prevent_destroy = true
    ignore_changes  = [password, attributes]  # Don't reset flow-managed state
  }
}

OIDC Login Returns "Permission Denied"

Symptom: Clicking OIDC login (e.g., Vault) redirects to Authentik, which shows "Permission denied" for the user. The provider popup may close with "Authentication failed: The provider window was closed before authentication was complete."

Root Cause: The application has a policy binding for a base group (e.g., vault-users), but the user is only in the admin group (e.g., vault-admin). Authentik's group parent hierarchy (parents M2M field) is supposed to grant inherited membership, but a known bug (#15159) causes parent assignments to silently fail.

The bug is in a PostgreSQL trigger: REFRESH MATERIALIZED VIEW CONCURRENTLY on the GroupParentageNode table causes lock contention that silently rolls back M2M parent writes. The API returns 200 OK and the Terraform provider reports success, but parents are empty.

Diagnosis:

  1. Check Authentik server logs (Grafana/Loki):

    # Look for policy engine results
    P_ENG: Found static bindings ... total: N, passing: 0
    PolicyAccessView user_has_access passing=False user=USERNAME app=APPNAME
    

  2. Verify group membership in Authentik Admin UI:

  3. Admin UI → Directory → Users → select user → Groups tab
  4. Check if the user is in the base group (not just admin)

  5. Verify parent hierarchy:

  6. Admin UI → Directory → Groups → select admin group → Update
  7. Check if "Parents" shows the base group (likely empty due to bug)

Fix: Add policy bindings for both the base group AND the admin group on each application. Since policy_engine_mode = "any", either binding grants access. See Group Management > Access Control Pattern above.

Affected versions: Authentik 2025.12.x (bug present since parentparents M2M migration)

Login Failures

  1. Check user is active
  2. Verify group membership (including policy bindings — see above)
  3. Review Authentik logs in Grafana

OIDC Issues

  1. Check application configuration
  2. Verify redirect URIs
  3. Check policy bindings (see "OIDC Login Returns Permission Denied" above)
  4. Test with Authentik built-in debug tools

See Also