4.4 KiB
4.4 KiB
Forward Authentication
References:
- https://www.reddit.com/r/selfhosted/comments/1hybe81/i_wanted_to_implement_my_own_forward_auth_proxy/
- https://www.kevinsimper.dk/posts/implementing-a-forward_auth-proxy-tips-and-details
Overview
Forward authentication allows a reverse proxy (like Caddy, Nginx, Traefik) to delegate authentication decisions to a separate service. Clinch implements this pattern to provide SSO for multiple applications.
Key Implementation Details
Tip 1: Forward URL Configuration ✅
Clinch includes the original destination URL in the redirect parameters:
login_params = {
rd: original_url, # redirect destination
rm: request.method # request method
}
login_url = "#{base_url}/signin?#{login_params.to_query}"
Example: https://clinch.aapamilne.com/signin?rd=https://metube.aapamilne.com/&rm=GET
Tip 2: Root Domain Cookies ✅
Clinch sets authentication cookies on the root domain to enable cross-subdomain authentication:
def extract_root_domain(host)
# clinch.aapamilne.com -> .aapamilne.com
# app.example.co.uk -> .example.co.uk
# localhost -> nil (no domain restriction)
end
cookies.signed.permanent[:session_id] = {
value: session.id,
httponly: true,
same_site: :lax,
secure: Rails.env.production?,
domain: ".aapamilne.com" # Available to all subdomains
}
This allows the same session cookie to work across:
clinch.aapamilne.com(auth service)metube.aapamilne.com(protected app)sonarr.aapamilne.com(protected app)
Authelia Analysis
Implementation Comparison
Authelia Approach (from analysis of tmp/authelia/):
- Returns
302 Foundor303 See OtherwithLocationheader - Direct browser redirects (bypasses some proxy logic)
- Uses StatusFound (302) or StatusSeeOther (303)
Clinch Current Implementation:
- Returns
302 Founddirectly to login URL (matching Authelia) - Includes
rd(redirect destination) andrm(request method) parameters - Uses root domain cookies for cross-subdomain authentication
How Clinch Forward Auth Works
Authentication Flow
- User visits
https://metube.aapamilne.com/ - Caddy forwards to
http://clinch:9000/api/verify?rd=https://clinch.aapamilne.com - Clinch checks session:
- If authenticated: Returns
200 OKwith user headers - If not authenticated: Returns
302 Foundto login URL with redirect parameters
- If authenticated: Returns
- Browser follows redirect to Clinch login page
- User logs in → gets redirected back to original MEtube URL
- Caddy tries again → succeeds and forwards to MEtube
Response Headers
Successful Authentication (200 OK):
Remote-User: user@example.com
Remote-Email: user@example.com
Remote-Groups: media-managers,users
Remote-Admin: false
Redirect to Login (302 Found):
Location: https://clinch.aapamilne.com/signin?rd=https://metube.aapamilne.com/&rm=GET
Caddy Configuration
# Clinch SSO (main authentication server)
clinch.aapamilne.com {
reverse_proxy clinch:9000
}
# MEtube (protected by Clinch)
metube.aapamilne.com {
forward_auth clinch:9000 {
uri /api/verify?rd=https://clinch.aapamilne.com
copy_headers Remote-User Remote-Email Remote-Groups Remote-Admin
}
handle {
reverse_proxy * {
to http://192.168.2.223:8081
header_up X-Real-IP {remote_host}
}
}
}
Key Files
- Forward Auth Controller:
app/controllers/api/forward_auth_controller.rb - Authentication Logic:
app/controllers/concerns/authentication.rb - Caddy Examples:
docs/caddy-example.md - Authelia Analysis:
docs/authelia-forward-auth.md
Testing
# Test forward auth endpoint directly
curl -v http://localhost:9000/api/verify?rd=https://clinch.aapamilne.com
# Should return 302 redirect to login page
# Or 200 OK if you have a valid session cookie
Troubleshooting
Common Issues
- Authentication Loop: Check that cookies are set on the root domain
- Session Not Shared: Verify
extract_root_domainis working correctly - Caddy Connection: Ensure
clinch:9000resolves from your Caddy container
Debug Logging
Enable debug logging in forward_auth_controller.rb to see:
- Headers received from Caddy
- Domain extraction results
- Redirect URLs being generated
Rails.logger.info "ForwardAuth Headers: Host=#{host}, X-Forwarded-Host=#{original_host}"
Rails.logger.info "Setting 302 redirect to: #{login_url}"