Enterprise SSO Implementation
Built enterprise-grade Single Sign-On protecting multiple applications using forward authentication at the reverse proxy layer. This pattern works with ANY web application - even those without native SSO support - by intercepting requests at the network edge and validating sessions before proxying to backends.
The Problem
Initial State
- 8+ self-hosted applications (Bookstack, Glance, code-server, Portainer, etc.)
- Each app managing its own users and passwords
- No centralized access control
- No audit trail of who accessed what
- Password fatigue (same password reused, or forgot which password for which app)
Business Impact
- Security risk: Weak/reused passwords across services
- Operational overhead: Managing users in 8 different systems
- No way to revoke access globally when needed
- Inconsistent authentication experience
The Solution
Architecture Overview
┌─────────────┐
│ End User │
│ Browser │
└──────┬──────┘
│ 1. HTTPS request
▼
┌─────────────────────────┐
│ Nginx Proxy Manager │ ← SSL termination, initial request
│ (Reverse Proxy) │
└──────┬──────────────────┘
│ 2. auth_request (nginx subrequest)
▼
┌─────────────────────────┐
│ Authentik Outpost │ ← Session validation
│ (Forward Auth) │
└──────┬──────────────────┘
│ 3. If no session → redirect to login
│ If valid session → return 200 + headers
▼
┌─────────────────────────┐
│ Authentik Server │ ← Login flows, LDAP queries
│ (Identity Provider) │
└──────┬──────────────────┘
│ 4. LDAP authentication
▼
┌─────────────────────────┐
│ Active Directory │ ← User/group directory
│ (Identity Source) │
└─────────────────────────┘
How It Works (Step-by-Step)
Unauthenticated Request:
- User navigates to
https://docs.homelab.local - Nginx receives request, makes internal
auth_requestto Authentik outpost - Outpost checks for valid session cookie → None found → Returns
401 Unauthorized - Nginx catches the 401, redirects browser to Authentik login page
- User enters credentials (validated against Active Directory via LDAP)
- Authentik creates session, sets cookie, redirects back to original URL
- Request repeats (now with session cookie) → Outpost validates → Returns
200 OK - Nginx proxies request to backend application with user identity headers
- User sees the protected application
Authenticated Request:
- Every subsequent request includes session cookie
- Outpost validates instantly (no AD query needed - session already established)
- Application receives authenticated request with user info in headers
Technical Implementation
Component Stack
Identity Layer:
- Active Directory: 2 domain controllers (DC01, DC02) for redundancy
- LDAP Protocol: StartTLS encryption (port 389) for AD queries
- Authentik: Modern IdP with web UI for managing auth flows
- Distributed Architecture: Main Authentik server + outpost for forward auth
Network Layer:
- VLAN Segmentation: Infrastructure (10.x.x.0/24), Clients, IoT, Guests
- Firewall Policies: Zone-based rules with default-deny stance
- Reverse Proxy: Nginx Proxy Manager with SSL/TLS termination
Application Layer:
- Docker Networks: Internal
proxy-netbridge for container communication - Protected Apps: Bookstack (docs), Glance (dashboard), code-server (IDE)
- Centralized Certs: Let's Encrypt wildcard via Cloudflare DNS challenge
Key Technical Decisions
- Forward auth works with any HTTP(S) application (no app-level changes needed)
- Application never sees credentials (security)
- Centralized at network edge (defense in depth)
- Single pattern scales to unlimited apps
- Trade-off: Double authentication if app has native auth (acceptable in homelab)
- StartTLS uses standard port 389 (no firewall rule changes)
- Encrypts after initial connection (opportunistic TLS)
- Simpler certificate management for internal CA
- Enterprise standard for internal LDAP
- Outpost co-located with applications (low latency auth checks)
- Server handles heavy lifting (login flows, LDAP queries, UI)
- Separation of concerns (auth validation vs identity management)
- Mirrors enterprise IdP architecture (e.g., Okta agents)
The Challenges
Symptom:
Error initializing SSL/TLS LDAP bind failed with error: Invalid credentials
Root Cause Discovery:
- Initially used User Principal Name (UPN) format:
svc-authentik@homelab.local - LDAP bind operations require Distinguished Name (DN) format
- UPN is Windows/Kerberos-specific, not universal LDAP
Investigation Process:
- Verified service account password in AD (correct)
- Tested network connectivity to DC (reachable)
- Read Microsoft LDAP documentation on bind requirements
- Discovered DN vs UPN distinction
- Changed bind credential format
Solution:
svc-authentik@homelab.local (UPN)
CN=svc-authentik,CN=Users,DC=homelab,DC=local (DN)
Symptom:
500 Internal Server Error nginx error log: "auth request unexpected status: 302"
Root Cause:
- nginx
auth_requestdirective expects either2xx(authenticated) or401/403(not authenticated) - Authentik outpost was returning
302 Redirectfor unauthenticated users - nginx treats unexpected status codes as errors
Solution:
Added error_page handler to nginx configuration:
location / {
auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin; # Catch 401, redirect gracefully
# ... rest of config
}
location @goauthentik_proxy_signin {
internal;
return 302 /outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
}
auth_request is powerful but opinionated. Understanding its behavior model (status code expectations, subrequest mechanics, error handling) is essential for implementing reverse proxy authentication patterns.
Symptom:
User successfully authenticated in Authentik, but kept getting redirected back to login page infinitely.
Root Cause:
- Authentik session cookie was being set with
SameSite=Strict - Redirect from Authentik back to protected app was cross-domain (different subdomain)
- Browser blocked cookie transmission due to
SameSitepolicy - Each request appeared unauthenticated, triggering new login redirect
Solution:
Changed Authentik cookie settings to SameSite=Lax:
- Allows cookies on same-site navigation (subdomain redirects)
- Still protects against CSRF attacks
- Balances security and functionality
SameSite attributes (None, Lax, Strict) is essential when implementing authentication flows that involve redirects across subdomains.
Outcomes & Impact
Measurable Results
- ✅ 8+ applications protected with centralized SSO
- ✅ Zero password sprawl - users authenticate once with AD credentials
- ✅ 5-minute onboarding for new applications (reusable pattern)
- ✅ 100% authentication success rate since deployment
- ✅ All authentication events logged centrally in Authentik
Skills Demonstrated
Enterprise Identity Architecture
- LDAP protocol and directory services
- Modern IdP patterns (Okta, Auth0, Azure AD)
- Forward auth vs native SSO trade-offs
- Distributed system design
Authentication Protocols
- LDAP bind operations (DN vs UPN)
- TLS/SSL for LDAP traffic (StartTLS)
- Session management and cookies
- OAuth2/OIDC concepts
Reverse Proxy Engineering
- nginx auth_request subrequest model
- HTTP status code handling
- Header manipulation
- Buffer sizing for large headers
Systematic Troubleshooting
- Log analysis across distributed systems
- Protocol-level debugging
- Root cause analysis
- Documentation-driven problem solving
Reusable Pattern
The power of this implementation is the reusable pattern. Adding SSO to a new application takes ~5 minutes:
- In Authentik: Create Proxy Provider + Application (3 clicks)
- In Authentik: Assign application to outpost (1 click)
- In NPM: Add
auth_requestlocation blocks to proxy host (paste config) - Test: Access app → redirect to login → authenticate → access granted
This pattern has been applied to:
- Documentation platform (Bookstack)
- Dashboard (Glance)
- Web IDE (code-server)
- Monitoring (Uptime Kuma)
- Backup tool (Duplicati)
And can protect ANY future HTTP(S) application without modification.
Interview Story (STAR Format)
"I was managing a homelab with 8 self-hosted applications, each with independent user management. This created password fatigue, security risks from reused credentials, and no centralized access control or audit trail."
"Implement enterprise-grade Single Sign-On that would work across all applications - including those without native SSO support - while maintaining security and providing a foundation for future access policies."
"I implemented forward authentication using Authentik as the identity provider, backed by Active Directory as the user directory. The architecture places authentication at the reverse proxy layer, so every HTTP request is validated before reaching the application. This required:
- Setting up a 2-DC Active Directory environment with LDAP
- Deploying Authentik with a distributed architecture (main server + outpost)
- Configuring nginx to intercept requests and validate sessions via subrequests
- Troubleshooting LDAP bind failures (DN vs UPN format issue)
- Debugging nginx auth_request status code handling
- Resolving session cookie propagation through proxy layers
The key technical challenges were understanding LDAP protocol requirements, nginx's auth_request model, and HTTP cookie flow through multi-layer proxies."
"Successfully implemented SSO protecting 8 applications with a reusable pattern that takes ~5 minutes to apply to new apps. Eliminated password sprawl, centralized authentication logging, and built a foundation for future enhancements like MFA and group-based access policies. The implementation mirrors enterprise patterns used by companies like Cloudflare Access or Okta + reverse proxy combinations."
What's Next
Immediate Improvements
- MFA: Enable TOTP or WebAuthn for second factor
- LDAPS: Migrate from StartTLS to dedicated LDAPS port 636
- Monitoring: Centralize auth logs in Loki/Grafana
- Group Policies: Implement fine-grained access control per app
Future Exploration
- User Self-Service: Password reset portal via Authentik
- Device Trust: Integrate device posture checks
- OAuth2 Device Flow: CLI tool authentication
- SAML Integration: Explore SAML for apps that don't support OIDC