Caddy module to require at-proto authentication and restrict routes to DIDs
1# Caddy Atproto Auth
2
3A Caddy module that provides Identity-Aware Proxy (IAP) capabilities via AT Protocol OAuth 2.1. Dedicate an auth portal, place your gates, and define the handles or DIDs that should be allowed through (or `allow *`).
4
5## Usage
6
7Build a custom Caddy binary with `xcaddy`:
8
9```bash
10xcaddy build \
11 --with tangled.org/vvill.dev/caddy-atproto-auth
12```
13
14## Quick Start
15
16### Single App
17
18```caddyfile
19{
20 app.example.com {
21 route {
22 atproto_portal {
23 # So it doesn't conflict with your /login
24 path_prefix atproto_auth
25 }
26 atproto_gate {
27 # Specify handles/DIDs here or just require login
28 allow @vvill.dev @tangled.org
29 # If you need path_prefix above
30 portal_url https://app.example.com/atproto_auth
31 }
32 # Whatever your app is
33 reverse_proxy localhost:8080
34 }
35 }
36}
37```
38### Multiple Apps
39
40```caddyfile
41{
42 auth.example.com {
43 route {
44 atproto_portal {
45 cookie_domain example.com
46 allowed_redirect_domains app1.example.com
47 }
48 }
49 }
50 # Repeat this for N apps :)
51 app1.example.com {
52 route {
53 atproto_gate {
54 allow *
55 portal_url https://auth.example.com
56 cookie_domain example.com
57 }
58 # Whatever your app is
59 reverse_proxy localhost:8081
60 }
61 }
62}
63```
64
65## Full Configuration
66
67### Global Options
68
69```caddyfile
70{
71 # Optional
72 atproto {
73 # Allow PDSs to resolve to local IPs (within provided CIDR ranges)
74 # You'll need this if you run your own PDS on this computer or network.
75 # Default: None
76 allow_private_cidrs 127.0.0.1/8 ::1/128 192.168.0.0/16
77
78 # Path to the SQLite database.
79 # Default: atproto.db
80 storage_path /var/lib/caddy/atproto.db
81
82 # A random 32+ character string used to sign session cookies.
83 # For use where a Gate and its Portal are on different machines.
84 # Default: 32 random bytes
85 cookie_secret "change-me-to-a-secure-random-string-at-least-32-chars"
86
87 # OAuth Session duration - how long before user needs to log in again
88 # Default 1 week (7d)
89 session_duration 1d
90
91 # Number of OAuth managers in cache (to avoid dos, increase with scale)
92 # Default: 100
93 oauth_manager_cache_size 1000
94 }
95}
96```
97
98### Authentication Portal (`atproto_portal`)
99
100The `atproto_portal` directive configures the central authentication server. This handles the OAuth flow, serves the login page, and issues session cookies.
101
102You can replace the html templates with your own.
103- The login page:
104 - May show `{{ .AppName }}` and `{{ .Error }}`
105 - POSTs to `{{ .LoginURL }}` with `handle` and `redirect_to`
106 - Use: `<input type="hidden" name="redirect_to" value="{{ .Redirect }}" />`
107- The forbidden page:
108 - May show `{{ .AppName }}`, `{{ .DID }}` and `{{ .Handle }}`
109 - Should link to `{{ .LogoutURL }}`
110
111```caddyfile
112auth.example.com {
113 atproto_portal {
114 # Required when the portal isn't
115 # on the same subdomain as the gate
116 cookie_domain example.com
117
118 # The display name shown on the login page.
119 # Default: "Authentication Portal"
120 name "My Services"
121
122 # Path to a custom HTML template for the login page.
123 login_template /path/to/login.html
124 # Path to a custom HTML template for the access denied page.
125 forbidden_template /path/to/forbidden.html
126
127 # Prepends a prefix to /login and /logout paths.
128 # Useful to avoid conflicts with downstream apps.
129 path_prefix atproto_auth
130
131 # Allowed domains for the redirect_to parameter after login/logout.
132 # By default, only the portal's domain is allowed.
133 allowed_redirect_domains app.example.com *.app.example.com
134
135 # Where to redirect users after they log out.
136 # Default: The login page
137 logout_redirect_url https://example.com/goodbye
138
139 # Change cookie name, also to avoid conficts
140 # Default: atproto_session
141 cookie_name caddy_atproto_session
142 }
143}
144```
145
146### Authentication Gate (`atproto_gate`)
147
148The `atproto_gate` directive protects your services. It verifies the session cookie and enforces access control.
149
150If you set `cookie_domain` or `cookie_name` in this gate's portal, it needs to be set here as well.
151
152```caddyfile
153app.example.com {
154 atproto_gate {
155 # List of allowed identities (DIDs or Handles).
156 allow @alice.selfhosted.social
157 allow did:plc:1234abcd...
158 # Allow all (still requires sign in)
159 allow *
160
161 # If the Portal uses a path_prefix, include it here
162 # Default: /
163 portal_url https://auth.example.com/atproto_auth
164
165 # Match Portal's values if set. Used for token refresh.
166 cookie_domain example.com
167 cookie_name caddy_atproto_session
168
169 # The plugin resolves all handles to DIDs at startup. For security
170 # against account hijacking, dynamic handle resolution is disabled by default.
171 # Enable this if you want to follow specified handles to new DIDs.
172 # Default: false
173 resolve_handles_on_request false
174 }
175}
176```