<% end %>
diff --git a/app/views/admin/forward_auth_rules/edit.html.erb b/app/views/admin/forward_auth_rules/edit.html.erb
index 7796ccb..6c3d302 100644
--- a/app/views/admin/forward_auth_rules/edit.html.erb
+++ b/app/views/admin/forward_auth_rules/edit.html.erb
@@ -45,6 +45,75 @@
Select groups that are allowed to access this domain. If no groups are selected, all authenticated users will be allowed access (bypass).
\ No newline at end of file
diff --git a/app/views/admin/forward_auth_rules/new.html.erb b/app/views/admin/forward_auth_rules/new.html.erb
index 7f15374..cd3f16d 100644
--- a/app/views/admin/forward_auth_rules/new.html.erb
+++ b/app/views/admin/forward_auth_rules/new.html.erb
@@ -45,6 +45,75 @@
Select groups that are allowed to access this domain. If no groups are selected, all authenticated users will be allowed access (bypass).
+ <% effective_headers.each do |key, header_name| %>
+
+
<%= key.to_s.capitalize %>
+
+ <%= header_name %>
+
+
<% end %>
-
Inactive rules are ignored during authentication
-
-
+
+ <% end %>
+
+
+
+
+
+
+
+
Access Control
+
+
Allowed Groups
+
+ <% if @allowed_groups.empty? %>
+
+
+
+
+ No groups assigned - all active users can access this domain.
+
+
+
+
+ <% else %>
+
+ <% @allowed_groups.each do |group| %>
+
+
+
<%= group.name %>
+
<%= pluralize(group.users.count, "member") %>
+
+
+ <% end %>
+
+ <% end %>
+
diff --git a/app/views/invitations_mailer/invite_user.html.erb b/app/views/invitations_mailer/invite_user.html.erb
new file mode 100644
index 0000000..9e2c21f
--- /dev/null
+++ b/app/views/invitations_mailer/invite_user.html.erb
@@ -0,0 +1,12 @@
+
+ You've been invited to join Clinch! To set up your account and create your password, please visit
+ <%= link_to "this invitation page", invite_url(@user.invitation_login_token) %>.
+
+
+
+ This invitation link will expire in <%= distance_of_time_in_words(0, @user.invitation_login_token_expires_in) %>.
+
+
+
+ If you didn't expect this invitation, you can safely ignore this email.
+
\ No newline at end of file
diff --git a/app/views/invitations_mailer/invite_user.text.erb b/app/views/invitations_mailer/invite_user.text.erb
new file mode 100644
index 0000000..572a1fc
--- /dev/null
+++ b/app/views/invitations_mailer/invite_user.text.erb
@@ -0,0 +1,8 @@
+You've been invited to join Clinch!
+
+To set up your account and create your password, please visit:
+#{invite_url(@user.invitation_login_token)}
+
+This invitation link will expire in #{distance_of_time_in_words(0, @user.invitation_login_token_expires_in)}.
+
+If you didn't expect this invitation, you can safely ignore this email.
\ No newline at end of file
diff --git a/db/migrate/20251026033102_add_headers_config_to_forward_auth_rule.rb b/db/migrate/20251026033102_add_headers_config_to_forward_auth_rule.rb
new file mode 100644
index 0000000..0ae9182
--- /dev/null
+++ b/db/migrate/20251026033102_add_headers_config_to_forward_auth_rule.rb
@@ -0,0 +1,5 @@
+class AddHeadersConfigToForwardAuthRule < ActiveRecord::Migration[8.1]
+ def change
+ add_column :forward_auth_rules, :headers_config, :json, default: {}, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 942fff7..62d0433 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.1].define(version: 2025_10_24_055739) do
+ActiveRecord::Schema[8.1].define(version: 2025_10_26_033102) do
create_table "application_groups", force: :cascade do |t|
t.integer "application_id", null: false
t.datetime "created_at", null: false
@@ -68,6 +68,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_10_24_055739) do
t.boolean "active"
t.datetime "created_at", null: false
t.string "domain_pattern"
+ t.json "headers_config", default: {}, null: false
t.integer "policy"
t.datetime "updated_at", null: false
end
diff --git a/docs/forward-auth.md b/docs/forward-auth.md
new file mode 100644
index 0000000..ffc39ce
--- /dev/null
+++ b/docs/forward-auth.md
@@ -0,0 +1,153 @@
+# 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:
+
+```ruby
+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:
+
+```ruby
+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 Found` or `303 See Other` with `Location` header
+- Direct browser redirects (bypasses some proxy logic)
+- Uses StatusFound (302) or StatusSeeOther (303)
+
+**Clinch Current Implementation:**
+- Returns `302 Found` directly to login URL (matching Authelia)
+- Includes `rd` (redirect destination) and `rm` (request method) parameters
+- Uses root domain cookies for cross-subdomain authentication
+
+## How Clinch Forward Auth Works
+
+### Authentication Flow
+
+1. **User visits** `https://metube.aapamilne.com/`
+2. **Caddy forwards** to `http://clinch:9000/api/verify?rd=https://clinch.aapamilne.com`
+3. **Clinch checks session**:
+ - **If authenticated**: Returns `200 OK` with user headers
+ - **If not authenticated**: Returns `302 Found` to login URL with redirect parameters
+4. **Browser follows redirect** to Clinch login page
+5. **User logs in** → gets redirected back to original MEtube URL
+6. **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
+
+```caddyfile
+# 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
+
+```bash
+# 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
+
+1. **Authentication Loop**: Check that cookies are set on the root domain
+2. **Session Not Shared**: Verify `extract_root_domain` is working correctly
+3. **Caddy Connection**: Ensure `clinch:9000` resolves 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
+
+```ruby
+Rails.logger.info "ForwardAuth Headers: Host=#{host}, X-Forwarded-Host=#{original_host}"
+Rails.logger.info "Setting 302 redirect to: #{login_url}"
+```
\ No newline at end of file