TekOnline

Fixing Authentik Email Confirmation OIDC Flow

The Problem

When using Authentik’s default enrollment flow with email confirmation, users who complete email verification are not properly authenticated with your OIDC application. Instead, they get redirected to Authentik’s login page and must enter their credentials again, even though they just completed registration.

Symptoms

  • ✅ User completes enrollment form
  • ✅ User receives and clicks email confirmation
  • ❌ User redirected to login page with “password incorrect” errors
  • ❌ Login fails even with correct credentials
  • ❌ User cannot authenticate at all after email confirmation

Why This Happens

The default Authentik enrollment flow is designed as a standalone process that doesn’t preserve OIDC authorization context through email confirmation. Here’s what happens:

  1. User starts OIDC authorization → Redirected to Authentik enrollment
  2. User completes enrollment → Email verification stage suspends flow
  3. User clicks email link → Email verification completes
  4. Flow ends → OIDC context is lost ❌
  5. User not authenticated in your app → Must login manually

The Solution: Direct OIDC Enrollment Flow

The solution is to modify the enrollment flow so it completes the OIDC authorization directly, without losing context.

Key Changes Required

  1. Remove redirect stages that break OIDC context
  2. Configure proper user activation sequence
  3. End flow with user login stage to complete OIDC authorization
  4. Preserve OIDC context throughout the entire process

Implementation Guide

Step 1: Export Your Current Enrollment Flow

  1. Go to Authentik Admin → Flows & Stages → Flows
  2. Find your enrollment flow (usually “Default enrollment flow”)
  3. Click Export → Download the YAML file
  4. Keep this as backup!

Step 2: Create the Fixed Flow Configuration

Create a new file fixed-enrollment-flow.yaml with the following configuration:

context: {}
entries:
- attrs:
    authentication: require_unauthenticated
    denied_action: message_continue
    designation: enrollment
    layout: stacked
    name: Fixed OIDC Enrollment Flow
    policy_engine_mode: any
    title: Welcome to authentik!
  conditions: []
  identifiers:
    pk: da34a483-4eb0-4e9b-b53b-9ce58229a7cf  # Use your flow's UUID
    slug: default-enrollment-flow              # Use your flow's slug
  model: authentik_flows.flow
  permissions: []
  state: present

# Your existing prompt fields (name, email, username, password, etc.)
# ... (keep all your existing prompt configurations)

# CRITICAL: User Write Stage Configuration
- attrs:
    create_users_as_inactive: false  # Create users as ACTIVE
    create_users_group: YOUR_GROUP_UUID
    user_creation_mode: always_create
    user_path_template: users
    user_type: internal
  conditions: []
  identifiers:
    name: default-enrollment-user-write
    pk: YOUR_USER_WRITE_STAGE_UUID
  model: authentik_stages_user_write.userwritestage
  permissions: []
  state: present

# Your existing prompt stage bindings
# ... (keep all your existing prompt stage configurations)

# CRITICAL: Email Stage Configuration
- attrs:
    activate_user_on_success: true   # Activate user on email confirmation
    from_address: system@authentik.local
    host: localhost
    port: 25
    subject: authentik
    template: email/account_confirmation.html
    timeout: 10
    token_expiry: minutes=30
    use_global_settings: true
  conditions: []
  identifiers:
    name: default-enrollment-email-verification
    pk: YOUR_EMAIL_STAGE_UUID
  model: authentik_stages_email.emailstage
  permissions: []
  state: present

# CRITICAL: User Login Stage (MUST be last stage)
- attrs:
    geoip_binding: no_binding
    network_binding: no_binding
    remember_me_offset: seconds=2592000
    session_duration: seconds=2592000
  conditions: []
  identifiers:
    name: default-enrollment-user-login
    pk: YOUR_LOGIN_STAGE_UUID
  model: authentik_stages_user_login.userloginstage
  permissions: []
  state: present

# Stage Bindings (adjust order numbers as needed)
# ... prompt stages at order 10, 11, etc.
# ... user write stage at order 20
# ... email stage at order 30
# CRITICAL: User login stage at order 100 (LAST)
- attrs:
    invalid_response_action: retry
    policy_engine_mode: any
    re_evaluate_policies: true
  conditions: []
  identifiers:
    order: 100  # MUST be the highest order (last stage)
    pk: YOUR_LOGIN_BINDING_UUID
    stage: YOUR_LOGIN_STAGE_UUID
    target: YOUR_FLOW_UUID
  model: authentik_flows.flowstagebinding
  permissions: []
  state: present

metadata:
  labels:
    blueprints.goauthentik.io/generated: 'true'
  name: Fixed OIDC Enrollment Flow
version: 1

Step 3: Update UUIDs in Your Configuration

You need to replace the placeholder UUIDs with your actual values:

  1. Get your flow UUID: From your exported flow, find the pk under the flow identifiers
  2. Get stage UUIDs: From your exported flow, copy the pk values for each stage
  3. Update the configuration with your actual UUIDs

Step 4: Critical Configuration Points

User Write Stage

create_users_as_inactive: false  # Users created as ACTIVE

Why: Users must be active to authenticate after email confirmation.

Email Stage

activate_user_on_success: true   # Confirms user legitimacy

Why: Ensures email verification activates/confirms the user account.

User Login Stage Position

order: 100  # MUST be the final stage

Why: This stage completes the OIDC authentication and returns tokens to your app.

Step 5: Import the Fixed Flow

  1. Go to Authentik Admin → Flows & Stages → Flows
  2. Click Import
  3. Upload your fixed-enrollment-flow.yaml
  4. Verify import success

Step 6: Test the Flow

  1. Start OIDC login from your application
  2. Click “Sign Up” to trigger enrollment flow
  3. Complete enrollment form
  4. Check email and click confirmation link
  5. Enter credentials on login page (authentication should now work)
  6. Verify successful redirect back to your application with authentication

Flow Diagram

User starts OIDC login from your app
↓
Redirected to Authentik enrollment flow
↓
User fills enrollment form (prompts)
↓
User account created (ACTIVE)
↓
Email verification sent
↓
User clicks email confirmation link
↓
Email stage completes successfully
↓
User login stage authenticates user
↓
User redirected to login page (but authentication now WORKS)
↓
User enters credentials and successfully logs in
↓
OIDC tokens generated and returned to your app ✅

Alternative Solutions

Option 1: Custom Email Template (Advanced)

If you prefer to keep redirect stages, you can create a custom email template that preserves OIDC context:

<!-- custom-enrollment-email.html -->
{% extends "email/base.html" %}
{% load i18n %}
{% load humanize %}

{% block content %}
<tr>
    <td class="content-block">
        <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
            <tbody>
                <tr>
                    <td align="center">
                        <table role="presentation" border="0" cellpadding="0" cellspacing="0">
                            <tbody>
                                <tr>
                                    <td>
                                        {% if request.GET.next %}
                                            <a id="confirm" href="{{ url }}&next={{ request.GET.next|urlencode }}" 
                                               rel="noopener noreferrer" target="_blank">
                                                {% trans 'Confirm Email' %}
                                            </a>
                                        {% else %}
                                            <a id="confirm" href="{{ url }}" 
                                               rel="noopener noreferrer" target="_blank">
                                                {% trans 'Confirm Email' %}
                                            </a>
                                        {% endif %}
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </td>
                </tr>
            </tbody>
        </table>
    </td>
</tr>
{% endblock %}

Option 2: Redirect Stage Approach (Complex)

You can use a redirect stage to transfer from enrollment to authentication flow, but this requires:

  1. Redirect stage configuration:- attrs: target_flow: YOUR_AUTH_FLOW_UUID keep_context: true mode: flow
  2. Proper UUID references
  3. Context preservation settings

Note: The direct approach (recommended solution) is simpler and more reliable.

Troubleshooting

Issue: “Password incorrect” after email confirmation

Cause: User created as inactive Solution: Set create_users_as_inactive: false

Issue: User redirected to login page after email confirmation

Expected Behavior: User should be able to log in successfully with their credentials Solution: This is normal behavior – the fix ensures authentication works after email confirmation

Issue: Flow import fails with UUID errors

Cause: Using placeholder UUIDs Solution: Replace all UUIDs with your actual flow/stage UUIDs

Cause: Email stage misconfiguration Solution: Verify activate_user_on_success: true and email settings

Issue: User not authenticated in your application

Cause: OIDC tokens not returned Solution: Verify user login stage completes the flow

Security Considerations

User Activation Strategy

Recommendedcreate_users_as_inactive: false + activate_user_on_success: true

  • Users are created active but must verify email to complete OIDC flow
  • Provides security through email verification requirement
  • Allows seamless authentication after confirmation

Alternativecreate_users_as_inactive: true + activate_user_on_success: true

  • Users inactive until email verification
  • Slightly more secure but same end result for OIDC flows

Email Verification

  • Email verification is required for OIDC completion
  • Users cannot get application tokens without confirming email
  • Unverified users cannot complete the enrollment flow

Common Pitfalls

  1. Wrong stage order: User login stage must be last
  2. Missing UUIDs: Must use actual UUIDs, not placeholders
  3. Inactive users: Must create users as active or activate on email success
  4. Flow context loss: Using redirect stages incorrectly
  5. Multiple flows: Ensure you’re modifying the correct enrollment flow

Best Practices

  1. Always backup your original flow before modifications
  2. Test thoroughly in a non-production environment first
  3. Use descriptive flow names to distinguish fixed flows
  4. Monitor logs during testing for error details
  5. Document your changes for future reference

Conclusion

The direct OIDC enrollment approach eliminates the authentication failures that plague the default Authentik enrollment flow. By properly configuring user activation and stage sequencing, users get a working registration → email verification → login experience.

Key improvement: After email confirmation, users are redirected to the login page where their credentials actually work (instead of failing with “password incorrect” errors). Once they log in, they’re successfully authenticated with your OIDC application.

This solution has been tested and works reliably for OIDC applications that require email verification during user enrollment.


Questions or Issues?

  • Check Authentik logs for detailed error messages
  • Verify all UUIDs are correct in your configuration
  • Ensure your OIDC application settings are correct
  • Test with a fresh user account

Contributing If you find improvements to this guide or encounter edge cases, please contribute back to help other developers facing this common issue.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *