Implementing Domain-Level Forward Authentication with Authentik
Introduction
In my previous posts on Authentik, I’ve covered setting up the platform, managing it with Terraform, and integrating it with services like Grafana, MinIO, and AWS IAM Identity Center.
Today, I’ll explain how I implemented domain-level forward authentication to protect multiple applications in my Kubernetes cluster using Authentik’s forward auth provider and NGINX ingress controller.
Understanding Forward Authentication
Forward authentication is a pattern where a proxy (like NGINX) delegates the authentication decision to an external service (in our case, Authentik). This approach allows you to protect multiple applications without modifying their code or configurations.
Authentik offers two forward auth modes:
- Forward domain mode: Authenticates at the domain level, allowing users to access all applications on the same domain after a single login
- Forward single mode: Authenticates each application separately, requiring explicit authorization for each
For my cluster, I chose the domain-level approach as it provides a smoother user experience while still maintaining security.
Configuring Authentik with Terraform
Let’s start by examining my Terraform configuration for Authentik’s forward auth provider:
# https://docs.goauthentik.io/docs/add-secure-apps/outposts/embedded/
resource "authentik_outpost" "embedded" {
name = "authentik Embedded Outpost"
type = "proxy"
protocol_providers = [
authentik_provider_proxy.nginx_forward_auth.id
]
service_connection = "403a40bd-821a-4a5b-9a89-ba3938e1fa8e"
}
resource "authentik_provider_proxy" "nginx_forward_auth" {
name = "nginx-forward-auth"
external_host = "https://authentik.apps.timvw.be"
authorization_flow = data.authentik_flow.default_authorization_flow.id
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
# Allow all domains in your cluster to use this provider
cookie_domain = ".apps.timvw.be"
# Security settings
mode = "forward_domain"
skip_path_regex = "^/(api|outpost\\.goauthentik\\.io)/.*$"
}
resource "authentik_application" "forward_auth" {
name = "Forward Authentication"
slug = "forward-auth"
protocol_provider = authentik_provider_proxy.nginx_forward_auth.id
meta_description = "Forward Authentication for NGINX Ingress"
meta_launch_url = "blank://blank"
lifecycle {
ignore_changes = [
meta_icon,
]
}
}
There are a few important components to note:
- Embedded Outpost: This is a component that runs inside Authentik and handles the forward authentication requests
- Proxy Provider: This configures how authentication works, including:
mode = "forward_domain"
: Users only need to authenticate once per domaincookie_domain = ".apps.timvw.be"
: Share authentication across all subdomainsskip_path_regex
: Paths that bypass authentication, like API endpoints
- Application: This defines the application in Authentik’s UI
Importing the Embedded Outpost
A challenge I faced was that the embedded outpost already exists within Authentik, but Terraform doesn’t know about it. To manage it with Terraform, I needed to import the existing resource.
The Authentik Terraform provider had an issue (#341) where finding the correct IDs for the embedded outpost was difficult. To resolve this, I monitored network requests in Chrome Developer Tools while navigating to the outposts section in the Authentik admin interface. This allowed me to find:
- The outpost ID (
pk
):8e7ee1c7-e3d1-4198-aa41-a6b60bf0e2db
- The service connection object ID (
service_connection_obj.pk
):403a40bd-821a-4a5b-9a89-ba3938e1fa8e
In my Terraform code, I used these IDs to import the existing resource:
import {
id = "8e7ee1c7-e3d1-4198-aa41-a6b60bf0e2db"
to = authentik_outpost.embedded
}
Configuring NGINX Ingress Controller
With Authentik configured, the next step was to set up NGINX ingress to use forward authentication. Here’s an example from my Jaeger deployment:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jaeger
namespace: monitoring
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# Authentik forward authentication
kubernetes.io/ingress.class: public
nginx.ingress.kubernetes.io/auth-url: http://authentik-server.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://authentik.apps.timvw.be/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
spec:
tls:
- hosts:
- jaeger.apps.timvw.be
secretName: jaeger-apps-tls-secret
rules:
- host: jaeger.apps.timvw.be
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jaeger
port:
number: 16686
The key annotations are:
nginx.ingress.kubernetes.io/auth-url
: Points to the Authentik service within the clusternginx.ingress.kubernetes.io/auth-signin
: The URL for users to sign in (redirects here when unauthenticated)nginx.ingress.kubernetes.io/auth-response-headers
: Headers to pass from Authentik to the protected applicationnginx.ingress.kubernetes.io/auth-snippet
: Additional NGINX configuration, ensuring proper forwarding of host headers
How Forward Authentication Works
With this configuration in place, here’s the authentication flow:
- User attempts to access a protected application (e.g.,
jaeger.apps.timvw.be
) - NGINX intercepts the request and forwards it to Authentik via the auth-url
- Authentik checks if the user is authenticated:
- If authenticated, it returns a 200 response and NGINX allows the request to proceed
- If not authenticated, Authentik returns a 401, and NGINX redirects to the auth-signin URL
- After successful authentication at Authentik, the user is redirected back to the original URL
- Since they’re now authenticated for the domain, all subsequent requests to *.apps.timvw.be are allowed
Advantages of Domain-Level Authentication
This approach offers several benefits:
- Single sign-on experience: Users authenticate once and can access all protected applications
- Centralized access control: All authorization decisions happen in Authentik
- Minimal application changes: Applications don’t need built-in authentication support
- Consistent user experience: Same login flow for all applications
- Enhanced security: Applications are never directly exposed without authentication
Controlling Access to Applications
While domain-level authentication means a user can access all protected applications after logging in once, you might want to restrict certain applications to specific users or groups. Authentik handles this through its application permissions system:
- Create a group in Authentik (e.g., “Jaeger Users”)
- Set the Forward Authentication application to require membership in this group
- Only users in the specified group will be authorized to access the protected applications
Creating Application Shortcuts
In addition to protecting applications with forward authentication, I also wanted to provide users with easy access to these applications through the Authentik dashboard. To do this, I created application shortcuts:
resource "authentik_application" "jaeger" {
name = "Jaeger"
slug = "jaeger"
meta_icon = "https://raw.githubusercontent.com/jaegertracing/artwork/refs/heads/master/SVG/Jaeger_Logo_Final_PANTONE.svg"
meta_launch_url = "https://jaeger.apps.timvw.be"
open_in_new_tab = true
}
This creates a tile in the Authentik application portal that users can click to access Jaeger directly. The important aspects of this configuration are:
meta_launch_url
: Links to the protected applicationmeta_icon
: Provides a visual icon for the applicationopen_in_new_tab
: Opens the application in a new browser tab
Troubleshooting Forward Authentication
When implementing this solution, I encountered a few issues worth mentioning:
- Cookie Issues: Ensure your
cookie_domain
is correctly set to cover all applications - Redirect Problems: Check that the
auth-signin
URL correctly encodes the return URL - Header Passing: If applications need user information, verify that
auth-response-headers
includes all necessary headers - Path Exclusions: Adjust
skip_path_regex
if certain paths should bypass authentication (like health checks or API endpoints)
Conclusion
Implementing domain-level forward authentication with Authentik and NGINX ingress provides a robust, centralized authentication layer for all applications in my Kubernetes cluster. With minimal configuration, I’ve secured multiple services while providing a seamless experience for users.
The combination of Authentik’s flexible authentication capabilities and NGINX’s ingress features makes this approach powerful yet simple to implement. Using Terraform to manage the configuration ensures the setup is reproducible and can be version-controlled.
This integration completes my series on Authentik implementations, demonstrating how a single identity provider can secure various aspects of your infrastructure, from cloud services to internal applications.