Caddy
casdoor-forward-auth puts Casdoor single sign-on in front of any service behind Caddy, without changing the service. Caddy asks casdoor-forward-auth about every request through its built-in forward_auth directive: signed-in users reach the service with their identity in request headers, everyone else is sent to the Casdoor login page first.
The same service also works with Traefik and Nginx. The setup on this page was tested end to end with Caddy v2.11 and casdoor-forward-auth v2.0.
How it works
- Caddy sends every request for a protected site to
/authof casdoor-forward-auth. - With a valid session cookie,
/authanswers200with headers likeX-Forwarded-User, and Caddy copies them into the request to your service. - Without a session, page loads (
GET,HEAD) are redirected to Casdoor. Other requests (POST,PUT, ...) get401, since they can't follow a login redirect. Caddy returns these answers to the browser unchanged. - After the login, Casdoor redirects to
/callback. casdoor-forward-auth checks thestate, exchanges the authorization code, verifies the access token, stores the user in a signedHttpOnlysession cookie and sends the user back to the page they asked for.
Prerequisites
- Caddy v2
- A Casdoor instance (see Server Installation)
- Two host names pointing to Caddy, e.g.,
auth.example.comfor casdoor-forward-auth andapp.example.comfor the protected service
Step 1: Configure the Casdoor application
-
Create or edit an application in Casdoor.
-
Add the callback of casdoor-forward-auth to Redirect URLs:
https://auth.example.com/callback -
Note the Client ID and Client secret.
Step 2: Deploy casdoor-forward-auth and Caddy
Create a Caddyfile:
auth.example.com {
reverse_proxy casdoor-forward-auth:9999
}
app.example.com {
forward_auth casdoor-forward-auth:9999 {
uri /auth
copy_headers X-Forwarded-User X-Forwarded-User-Id X-Forwarded-Organization X-Forwarded-Email X-Forwarded-Groups X-Forwarded-Roles
}
reverse_proxy app:8080
}
app:8080 is the service you protect. To protect more services, give each site the same forward_auth block. Don't put forward_auth on the site of casdoor-forward-auth itself.
Create a docker-compose.yml:
services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
casdoor-forward-auth:
image: ghcr.io/casdoor/casdoor-forward-auth:latest
environment:
CASDOOR_ENDPOINT: https://door.casdoor.com
CLIENT_ID: <client ID>
CLIENT_SECRET: <client secret>
EXTERNAL_URL: https://auth.example.com
COOKIE_DOMAIN: example.com
COOKIE_SECRET: <random string of at least 32 characters>
app:
image: traefik/whoami
command: --port 8080
volumes:
caddy_data:
Replace the placeholders:
CASDOOR_ENDPOINT: the URL of your Casdoor serverCLIENT_IDandCLIENT_SECRET: the values from Step 1EXTERNAL_URL: the public URL of casdoor-forward-auth.<EXTERNAL_URL>/callbackmust be a Redirect URL of the applicationCOOKIE_DOMAIN: the parent domain of the protected hosts, so the session cookie is sent to all of themCOOKIE_SECRET: a random secret, e.g., fromopenssl rand -hex 32. Keep it stable: changing it signs everybody out
Caddy gets HTTPS certificates for both hosts automatically. Start the services:
docker compose up -d
Step 3: Test the integration
- Open the protected service, e.g.,
https://app.example.com. - You are redirected to the Casdoor login page.
- After signing in, you are back on the page you opened, and the service receives the identity headers.
traefik/whoamiprints them, so you can check them there.
Identity headers
| Header | Value |
|---|---|
X-Forwarded-User | User name, e.g., alice |
X-Forwarded-User-Id | User ID |
X-Forwarded-Organization | Organization of the user |
X-Forwarded-Email | Email address |
X-Forwarded-Groups | Comma-separated groups, e.g., built-in/dev,built-in/ops |
X-Forwarded-Roles | Comma-separated role names |
casdoor-forward-auth always returns all of them, possibly empty, so Caddy's copy_headers replaces whatever the client sent in the same headers. Make sure the protected service is only reachable through Caddy, otherwise anyone can send these headers directly.
To let in only some users, check X-Forwarded-Groups or X-Forwarded-Roles in your service, or in Caddy with a header matcher after forward_auth:
app.example.com {
forward_auth casdoor-forward-auth:9999 {
uri /auth
copy_headers X-Forwarded-User X-Forwarded-Groups X-Forwarded-Roles
}
@notAdmin not header_regexp X-Forwarded-Roles (^|,)admin(,|$)
respond @notAdmin "Forbidden" 403
reverse_proxy app:8080
}
Configuration and logout
casdoor-forward-auth is configured with the same environment variables for every reverse proxy; see Configuration on the Traefik page. /logout clears the session of casdoor-forward-auth (with ?rd=<url> to redirect afterwards); the user stays signed in to Casdoor.