Auth and Access
AppID has two doors with two different locks. Keep them straight and everything else follows.
Control plane β managing pools and users
Everything under dodil appid β¦ / https://api.dodil.io/v1/appid/β¦ is platform-authenticated: you sign in with dodil auth login and the gateway resolves your organization from your token. There is no --org flag and no organization field in any request.
Access is role-based:
| Role | Can |
|---|---|
appid.admin | Everything β create/delete pools, rotate keys, manage connections, policies, users, user stores |
appid.viewer | Read β get/list pools, connections, users |
| org owner | Always everything for their orgβs pools, with no explicit grant |
Grant the roles with platform IAM (dodil auth user grant-role), the same way as any other product role.
Fine-grained scopes behind the roles (what a permission check is actually named): CreatePool, GetPool, ListPools, DeletePool, RotatePoolKeys, ManageConnections, ListConnections, ManagePolicies, ManageUserStore, ManageAppUsers, ListAppUsers.
App plane β how end users authenticate
End users never see any of the above. They authenticate against the pool issuer (https://appid.dodil.io/{org}/{pool}), which is public and unauthenticated by design:
- Authorization code + PKCE (recommended) β your app redirects to
{issuer}/authorize, the user signs in on the hosted page, your app exchanges the returned code at{issuer}/token. PKCE S256 is mandatory; plain and no-PKCE are refused. Tokens never pass through the browser URL. - Direct grants β
POST {issuer}/tokenwithgrant_type=passwordorrefresh_token, for apps that own their login UI. JSON and form-encoded both accepted. - Self-service β
{issuer}/signup,{issuer}/recover(+ email link to/reset),{issuer}/verifyfor email confirmation.
The pool mints EdDSA JWTs your backend verifies locally against the pool JWKS β checking iss and aud β with no call back to AppID. Full endpoint contract: API Reference β Issuer API.
Redirect URIs are an allowlist
{issuer}/authorize redirects only to URIs listed in the poolβs settings.redirect_uris. An unlisted redirect_uri gets an error page, never a redirect β so a stolen client_id canβt exfiltrate codes to an attackerβs site.
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.
CORS is per-pool and exact
Browser JavaScript may call the JSON endpoints only from origins listed in policies.cors_allowed_origins. Matching is exact (scheme + host + port); no wildcard subdomains. No configured origins β no CORS headers β browser calls fail. Tokens are sent as Authorization: Bearer β AppID sets no auth cookies on your appβs behalf.
Anti-enumeration posture
Responses are shaped so an outsider canβt probe your user base: recover always answers 200 whether or not the email exists; signing in to a tenant youβre not a member of fails exactly like a wrong password.
Rate limits
Defaults, enforced per pool on the public surface (adjustable per pool in policies):
| Action | Budget |
|---|---|
| Login attempts | 10 / min / email |
| Signups | 60 / min |
| Password-recovery emails | 5 / hour / email |
| Reset / verify codes | 60 / hour |
/authorize opens | 120 / min / client IP |
Client self-registration (/register) | 20 / hour (max 100 clients per pool) |
Exceeding a budget returns 429; budgets are counted cluster-wide.
What AppID deliberately does not have
- No
dk_API keys for app users. For machine-to-machine calls between your apps, a pool has service clients (dodil appid service-client create <pool> --client-id <id> --role <r>;client_credentialsat{issuer}/token; the tokenβssubissvc:<client_id>). To call Dodil products (K3, Models, β¦), your server uses a platform service account from IAM, not a pool token. - No pool-token access to Dodil products β a pool JWT is only meaningful to your app.
- Password policy is minimum length only (8β128, per-pool) β there are no character-class rules today.