"I have an internal admin API. I want my whole team to reach it after logging in once with our OIDC provider — without writing any auth code in the API itself."
asd doesn't ship an SSO provider. What it ships is the slot:
caddy.authPolicy in your manifest tells the bundled
caddy-security module
to put a gatekeeper in front of the route. You bring the provider
(Authentik / Keycloak / Google / Okta / anything OIDC-shaped); asd
holds the door.
This recipe assumes you already have an OIDC provider running with a
client app registered for asd. If you don't, set one up first — most
self-hosted teams pick Authentik.
The pieces:
asd.yaml under caddy.apps.security.caddy.authPolicy: <policy-name> reference in the service'snet.manifest.yaml.The flow at runtime: user hits the service URL, caddy-security
notices no session cookie, redirects to the OIDC provider, user
logs in, provider redirects back, caddy-security sets the cookie,
the request reaches your API with a known identity.
asd.yaml# asd.yaml — additions only
network:
caddy:
apps:
security:
oauth:
providers:
my-oidc:
driver: generic
realm: my-oidc
client_id: "${{ env.OIDC_CLIENT_ID }}"
client_secret: "${{ env.OIDC_CLIENT_SECRET }}"
scopes: [openid, email, profile]
base_authorization_url: "${{ env.OIDC_ISSUER }}/application/o/authorize/"
metadata_url: "${{ env.OIDC_ISSUER }}/.well-known/openid-configuration"
authentication:
providers:
local:
realm: local
path: /etc/caddy/users.json
authorization:
policies:
api-users:
auth_url: "/oauth2/my-oidc/login"
crypto:
default_token_lifetime: 3600
access_list_rules:
- match:
method: any
allow: true
Put the OIDC credentials in .env (or .env.<mode> per
learn/04):
# .env
OIDC_ISSUER=https://auth.example.com
OIDC_CLIENT_ID=asd-admin-api
OIDC_CLIENT_SECRET=...
# packages/admin-api/net.manifest.yaml
id: admin-api
name: Admin API
endpoint:
url: "http://127.0.0.1:${{ env.ADMIN_API_PORT }}"
caddy:
hostRoute:
host: "admin-${{ macro.tunnelClientId() }}.${{ macro.tunnelEndpoint() }}"
authPolicy: api-users
authBypassPaths:
- /healthz # external monitor, no session
- /oauth2/* # caddy-security's own callback path
tunnel:
public: true
Two lines do the work: authPolicy: api-users activates the
gatekeeper, authBypassPaths lets the OIDC callback through
(otherwise caddy-security would try to authenticate its own
redirect, which loops forever).
The callback URL is your service's public URL plus
/oauth2/<provider-realm>/callback:
https://admin-xyz1.eu2.tn.example.com/oauth2/my-oidc/callback
Register that in the OIDC client app's "redirect URIs". Different
provider, same shape: caddy-security always serves the callback
under /oauth2/<realm>/callback.
$ asd net apply
✅ Apply complete: 0 seeded, 1 routes (admin-api with authPolicy=api-users)
Visit https://admin-xyz1.eu2.tn.example.com/. The flow:
https://auth.example.com/application/o/authorize/?.../oauth2/my-oidc/callback?code=....X-Token-Subject, X-Token-User-Email, X-Token-User-Name/healthz keeps returning 200 without a session. Everything else
prompts for login.
Caddy-security is the engine. asd's caddy.authPolicy is a
slot that points at one of the named policies in the
caddy-security apps.security.authorization.policies block. Adding
more policies (e.g. one for admins, one for read-only) means adding
more entries to that block in asd.yaml.
authBypassPaths is essential, not optional. Without it, the
OAuth callback hits the same auth check as everything else and
loops. Always include /oauth2/* in the bypass list.
The OIDC client ID, secret, issuer URL, and redirect URI are
provider-specific work. asd can't infer them; you register the
client in your provider's admin UI and copy the values into .env.
Authentik, Keycloak, Google, Okta, Auth0 all have slightly
different UIs for this; the OIDC spec is the same.
Tokens vs cookies. caddy-security defaults to session cookies
(good for browsers, bad for curl). For API-to-API calls, expose
a separate route with basicAuth: (see
learn/05) or set up
caddy-security's token authentication.
learn/05-add-authentication is the simpler path.authBypassPaths, or use a single service with the bypass list covering the read-only paths.reference/net-manifest — authPolicy, authBypassPaths, caddy.apps fields.