# 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`: ```bash xcaddy build \ --with tangled.org/vvill.dev/caddy-atproto-auth ``` ## Quick Start ### Single App ```caddyfile { 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 ```caddyfile { 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 ```caddyfile { # 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 }}` with `handle` and `redirect_to` - Use: `` - The forbidden page: - May show `{{ .AppName }}`, `{{ .DID }}` and `{{ .Handle }}` - Should link to `{{ .LogoutURL }}` ```caddyfile 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. ```caddyfile 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 } } ```