Skip to Content
We are live but in Staging 🎉
Quickstart

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-app

Everything 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/callback

You 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/login

Sign 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.permissions

That’s the whole trust story — no shared secrets, no callback to AppID per request. Details and other languages: verifying tokens.

Where next