Quickstart
Five minutes from nothing to a working login. You need the Dodil CLI and a signed-in session (dodil auth login).
1. Create a pool
dodil appid pool create my-app --display-name "My App" --with-local--with-local enables the email + password connection in the same call. The response carries the pool’s identity:
issuer https://appid.dodil.io/acme/my-app
jwks_uri https://appid.dodil.io/acme/my-app/.well-known/jwks.json
audience pool:my-appEverything an app needs to trust this pool hangs off the issuer — an OIDC discovery document lives at {issuer}/.well-known/openid-configuration.
There is no --org anywhere: the CLI acts in the organization your session belongs to.
2. Allow your app’s redirect URI
The authorization-code flow only ever redirects to URIs you have allow-listed:
dodil appid settings set my-app \
--redirect-uris "https://app.example.com/callback,http://localhost:5173/callback"settings set is also where signup, verification, and token lifetimes live — --allow-signup, --require-email-verification, --password-min-length, --access-ttl-secs, --refresh-ttl-secs.
If your frontend calls the JSON API directly (rather than only following redirects), also allow its origin for CORS — origins are a pool policy:
dodil appid pool update my-app \
--policies '{"cors_allowed_origins":["https://app.example.com","http://localhost:5173"]}'3. Create a user (or let them sign up)
# with a password
dodil appid user create my-app [email protected] --password 'S3curePass!'
# or as an invite — no password sends the user an email to set one
dodil appid user create my-app [email protected]Self-service works too: the hosted page at {issuer}/login has a Sign up tab (unless you set --allow-signup=false), and POST {issuer}/signup does the same over JSON.
4. Run the whole login flow, headless
pool test-flow drives authorize → credentials → code exchange exactly as a browser would, and prints the tokens:
dodil appid pool test-flow my-app [email protected] 'S3curePass!' \
--redirect-uri https://app.example.com/callbackYou get back an access token (an EdDSA JWT), a rotating refresh token, and — because the flow requests scope=openid — an id token. Decode the access token and you’ll find iss, aud, sub, email, app_roles, and permissions claims.
5. See it in a browser
Open the hosted page directly:
https://appid.dodil.io/acme/my-app/loginSign in as Jane. For the real app integration — the /authorize redirect, PKCE, and the code exchange — follow the hosted login recipe.
6. Verify a token in your backend
Your backend trusts the pool by fetching its JWKS once and checking iss + aud:
import { createRemoteJWKSet, jwtVerify } from 'jose'
const ISSUER = 'https://appid.dodil.io/acme/my-app'
const jwks = createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`))
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
audience: 'pool:my-app',
})
// payload.sub, payload.email, payload.app_roles, payload.permissionsThat’s the whole trust story — no shared secrets, no callback to AppID per request. Details and other languages: verifying tokens.
Where next
- Core Concepts — what pools, connections, tenants, and user stores actually are
- Recipes → Hosted login — wire the code flow into an SPA properly
- Recipes → Own your user store — keep users in your own K3 bucket
- CLI Guide — every
dodil appidcommand