Caddy Atproto Auth#
A 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 *).
Usage#
Build a custom Caddy binary with xcaddy:
xcaddy build \
--with tangled.org/vvill.dev/caddy-atproto-auth
Quick Start#
Single App#
{
app.example.com {
route {
atproto_portal {
# So it doesn't conflict with your /login
path_prefix atproto_auth
}
atproto_gate {
# Specify handles/DIDs here or just require login
allow @vvill.dev @tangled.org
# If you need path_prefix above
portal_url https://app.example.com/atproto_auth
}
# Whatever your app is
reverse_proxy localhost:8080
}
}
}
Multiple Apps#
{
auth.example.com {
route {
atproto_portal {
cookie_domain example.com
allowed_redirect_domains app1.example.com
}
}
}
# Repeat this for N apps :)
app1.example.com {
route {
atproto_gate {
allow *
portal_url https://auth.example.com
cookie_domain example.com
}
# Whatever your app is
reverse_proxy localhost:8081
}
}
}
Full Configuration#
Global Options#
{
# Optional
atproto {
# Allow PDSs to resolve to local IPs (within provided CIDR ranges)
# You'll need this if you run your own PDS on this computer or network.
# Default: None
allow_private_cidrs 127.0.0.1/8 ::1/128 192.168.0.0/16
# Path to the SQLite database.
# Default: atproto.db
storage_path /var/lib/caddy/atproto.db
# A random 32+ character string used to sign session cookies.
# For use where a Gate and its Portal are on different machines.
# Default: 32 random bytes
cookie_secret "change-me-to-a-secure-random-string-at-least-32-chars"
# OAuth Session duration - how long before user needs to log in again
# Default 1 week (7d)
session_duration 1d
# Number of OAuth managers in cache (to avoid dos, increase with scale)
# Default: 100
oauth_manager_cache_size 1000
}
}
Authentication Portal (atproto_portal)#
The atproto_portal directive configures the central authentication server. This handles the OAuth flow, serves the login page, and issues session cookies.
You can replace the html templates with your own.
- The login page:
- May show
{{ .AppName }}and{{ .Error }} - POSTs to
{{ .LoginURL }}withhandleandredirect_to- Use:
<input type="hidden" name="redirect_to" value="{{ .Redirect }}" />
- Use:
- May show
- The forbidden page:
- May show
{{ .AppName }},{{ .DID }}and{{ .Handle }} - Should link to
{{ .LogoutURL }}
- May show
auth.example.com {
atproto_portal {
# Required when the portal isn't
# on the same subdomain as the gate
cookie_domain example.com
# The display name shown on the login page.
# Default: "Authentication Portal"
name "My Services"
# Path to a custom HTML template for the login page.
login_template /path/to/login.html
# Path to a custom HTML template for the access denied page.
forbidden_template /path/to/forbidden.html
# Prepends a prefix to /login and /logout paths.
# Useful to avoid conflicts with downstream apps.
path_prefix atproto_auth
# Allowed domains for the redirect_to parameter after login/logout.
# By default, only the portal's domain is allowed.
allowed_redirect_domains app.example.com *.app.example.com
# Where to redirect users after they log out.
# Default: The login page
logout_redirect_url https://example.com/goodbye
# Change cookie name, also to avoid conficts
# Default: atproto_session
cookie_name caddy_atproto_session
}
}
Authentication Gate (atproto_gate)#
The atproto_gate directive protects your services. It verifies the session cookie and enforces access control.
If you set cookie_domain or cookie_name in this gate's portal, it needs to be set here as well.
app.example.com {
atproto_gate {
# List of allowed identities (DIDs or Handles).
allow @alice.selfhosted.social
allow did:plc:1234abcd...
# Allow all (still requires sign in)
allow *
# If the Portal uses a path_prefix, include it here
# Default: /
portal_url https://auth.example.com/atproto_auth
# Match Portal's values if set. Used for token refresh.
cookie_domain example.com
cookie_name caddy_atproto_session
# The plugin resolves all handles to DIDs at startup. For security
# against account hijacking, dynamic handle resolution is disabled by default.
# Enable this if you want to follow specified handles to new DIDs.
# Default: false
resolve_handles_on_request false
}
}