Caddy module to require at-proto authentication and restrict routes to DIDs
2

Configure Feed

Select the types of activity you want to include in your feed.

Go 83.1%
HTML 16.9%
26 1 0

Clone this repository

https://tangled.org/vvill.dev/caddy-atproto-auth https://tangled.org/did:plc:fodfgwa72st5kdv5tbcvxfmg
git@knot.vvill.dev:vvill.dev/caddy-atproto-auth git@knot.vvill.dev:did:plc:fodfgwa72st5kdv5tbcvxfmg

For self-hosted knots, clone URLs may differ based on your setup.



README.md

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 }} with handle and redirect_to
      • Use: <input type="hidden" name="redirect_to" value="{{ .Redirect }}" />
  • The forbidden page:
    • May show {{ .AppName }}, {{ .DID }} and {{ .Handle }}
    • Should link to {{ .LogoutURL }}
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
    }
}