Pools & configuration
pool
# create — --with-local enables email+password sign-in in the same call
dodil appid pool create my-app --display-name "My App" --with-local
dodil appid pool list
dodil appid pool get my-app # includes live migration status when one is running
# partial update: only the documents you pass change; "{}" clears one
dodil appid pool update my-app --display-name "My App (EU)" \
--policies '{"cors_allowed_origins":["https://app.example.com"]}'
dodil appid pool rotate-keys my-app # new signing key; old key stays in JWKS for cached verifiers
dodil appid pool delete my-app --yes # keeps bucket data if the store is a K3 bucket
dodil appid pool delete my-app --yes --wipe-bucket-datapool update takes any of --display-name, --policies, --settings, --branding, --role-catalog (inline JSON, @file, or -). The dedicated settings / branding / roles commands below are ergonomic wrappers over the same documents.
pool test-flow — the headless smoke test
Drives the full authorization-code + PKCE flow — authorize, credential submit, code exchange — without a browser, and prints the resulting tokens:
dodil appid pool test-flow my-app [email protected] 'S3curePass!' \
--redirect-uri https://app.example.com/callbackUse it after every configuration change; it exercises exactly what your app will.
settings
The pool’s behavioral knobs:
dodil appid settings get my-app
dodil appid settings set my-app \
--allow-signup=true \
--require-email-verification=true \
--password-min-length 10 \
--access-ttl-secs 900 \
--refresh-ttl-secs 2592000 \
--refresh-reuse-grace-secs 10 \
--redirect-uris "https://app.example.com/callback,http://localhost:5173/callback"| Flag | Default | Meaning |
|---|---|---|
--allow-signup | true | Self-service signup on the hosted page and /signup endpoint |
--require-email-verification | false | Block sign-in until the email link is clicked |
--password-min-length | 8 | 8–128; the only password rule |
--access-ttl-secs | 900 | Access-token lifetime |
--refresh-ttl-secs | 30 days | Refresh-family lifetime |
--refresh-reuse-grace-secs | 10 | Double-submit forgiveness window (0 = strict, max 60) |
--redirect-uris | — | The /authorize allowlist — required for the code flow. Exact URLs, or a * as the leftmost host label (see below) |
Redirect URIs. Matching is exact: scheme, host, port and path, with no query string. https is required except for http://localhost and http://127.0.0.1. One wildcard form is allowed: a single * as the whole leftmost host label, matching exactly one label. https://*.apps.example.com/.dodil/auth/callback matches https://fn1-org.apps.example.com/.dodil/auth/callback but not https://a.b.apps.example.com/.dodil/auth/callback; the path and port must still match exactly.
Assistant sign-in (cimd_enabled). Off by default. When on, assistants such as Claude.ai and ChatGPT identify themselves by a client metadata URL (CIMD) and need no approval: the person signs in and consents. It has no dedicated flag; set it in the settings document: dodil appid settings get my-app -o json, add "cimd_enabled": true, then dodil appid pool update my-app --settings @settings.json.
branding
One JSON document that themes the hosted login/reset/verify pages and the transactional emails:
dodil appid branding get my-app
dodil appid branding set my-app @branding.json{
"logo_url": "https://cdn.example.com/logo.svg",
"colors": { "primary": "#4f46e5", "background": "#ffffff" },
"dark": { "colors": { "background": "#0b0b12" } },
"layout": "card",
"copy": { "title": "Sign in to My App" },
"links": { "terms": "https://example.com/terms", "privacy": "https://example.com/privacy" },
"email": { "from_name": "My App", "accent": "#4f46e5" }
}All keys are optional; colors accept #hex / rgb() / hsl(), image URLs must be https. The hosted page picks branding up immediately (it’s served pre-auth from {issuer}/branding). Full schema in the hosted login recipe.
roles
The role → permission catalog, expanded into every token’s permissions[] claim:
dodil appid roles get my-app
dodil appid roles set my-app admin=users.write,billing.read editor=posts.write viewer=posts.read
dodil appid roles set my-app --file roles.jsonRenaming or re-scoping a role here re-shapes future tokens without touching any user.
connection
How users sign in. local (email + password), oauth:<provider> (e.g. oauth:google, oauth:microsoft) and oidc are implemented:
dodil appid connection add my-app local --enabled
dodil appid connection list my-app
dodil appid connection remove my-app local --yesAn external provider takes a --config JSON (read from stdin with --config -):
# 1. register this callback with the identity provider:
# https://appid.dodil.io/{org}/{pool}/authorize/callback
# 2. add the connection (config on stdin; the secret is write-only)
echo '{"client_id":"…","client_secret":"…",
"discovery_url":"https://accounts.google.com/.well-known/openid-configuration",
"scopes":"openid email profile"}' \
| dodil appid connection add my-app oauth:google --enabled --config -The client secret is write-only: it is never returned by connection list. saml and anonymous are reserved names but not implemented.