Skip to content
Talk to our solutions team

IAM

iam.svc is the platform’s authentication and identity service: it authenticates a caller against one tenant’s identities, mints that tenant’s token, and serves the identity entities and control plane behind it.

Five pieces, in the order a request meets them.

PieceWhat it is
RealmA named binding of an identity entity (users, patient, vendor) to the provider names allowed against it. Declared in realms.yaml, not a database table.
ProviderA named auth-method declaration — name, type, template, nothing else. It is a name gate, not credentials.
Claims builderEvery flow that authenticates a user funnels through one builder: same reserved claims, same tenant keyring, same token policy.
TokenPASETO v4.public signed with the tenant’s own Ed25519 key. Verification dual-accepts PASETO and legacy EdDSA JWT.
SessionStateless (access token + rotating refresh token) or managed (opaque server-side reference exchanged per request). One preset picks which.

A realm resolves from the login body’s realm, else the default realm, else the literal users. The minted sub is the row id in that realm’s entity, and the realm claim rides along.

Every route except /ready and /health runs the same chain:

security headers + 1 MiB body cap
→ request timeout
→ tenant middleware (X-Customer, X-Product, X-Env, X-Tenant → CPET key)
→ auth middleware (Bearer PASETO/JWT, or ApiKey <id>.<secret>)
→ router

The tenant key selects everything downstream: connection pool, composed schema, script runtime, behavior config and signing keyring are all built and cached per CPET. A token is verified against the keyring of the tenant the request names, and its tenant claim must match that CPET — there is no cross-tenant token and no operator exception.

Miss a header and the tenant middleware answers 400 with the plain-text body invalid tenant — not JSON. Name a CPET with no resolvable datastore config and the failure surfaces later, as 500 engine_unavailable. Both are covered in Errors.

Routes mount at the router root. There is no /v2 prefix. Full surface: HTTP API reference.

The block ownsYou configure
Password verification (argon2id), refresh rotation with family-wide reuse detectionWhich entity each realm authenticates, and under which provider names
TOTP enrolment and the MFA gateAccess-token lifetime and custom claims
Token minting, key custody, rotation, per-tenant JWKSSession preset, concurrency ceiling, idle and absolute timeouts
Managed-session registry, revocation, CPET bindingNamed authorization predicates over identities
Per-(tenant, IP) rate limiting on brute-forceable auth routesPer-CPET pools, schemas, and the product-file source
The control plane, operator grants and impersonationIdentity fields, via schema extension

Identity fields are ordinary schema fields, so the columns a user may log in with are declared on the field itself (useforauth, useforidentity) rather than in an auth config. See Identity.

A product author writes an iam/ folder, served through meta (or meta.localdir in development). Six file types are read; anything else in the folder is ignored by the behavior loader.

FileDeclares
realms.yamlRealms: entity, allowed provider names, active, default
providers.yamlProvider names (name, type, template)
token.yamlexpiry, static claims, row-derived claims_from
session.yamlpreset, and the advanced model/timeout/concurrency axes
access.yamlNamed authorization predicates
config.yamlFree-form service properties and feature toggles

Each composes in four layers: built-in defaults ⊕ the product’s iam/ file ⊕ the tenant’s tenant_extensions row at /<type>.yaml ⊕ the tenant node’s session: block. Merging is last-write-wins per named item and per set field. A missing layer is normal; a malformed layer aborts the tenant build.

The shipping binary never registers an OAuth provider or a WebAuthn relying party, and there is no configuration key that does it:

SurfaceWhat a caller gets
GET /auth/oauth/:provider/start and /callback404 unknown_provider, for every provider name
POST /auth/webauthn/register/*, /assert/*503 webauthn_disabled

Two more limits worth knowing before you design around them:

  • There is no OTP login provider. No OTP over SMS, email or WhatsApp, and no OTP second factor. MFA has exactly one method, TOTP.
  • crypto_suite: v3 and preset: fedramp do not change the signing primitive. They parse and compose, but every token minted is PASETO v4.public over Ed25519; the timeout and session-model parts of the preset do take effect.
ItemValue
Binaryiam.svc
Dev port5030
Route prefixnone — /auth/login, /rest/:entity, /schema
Unauthenticated, header-free routesGET /ready, GET /health
Product sourcethe iam/ folder, via meta or meta.localdir
Storageone PostgreSQL pool per CPET; database-per-tenant or schema-per-tenant
Signing keysVault-custodied, inline PEM (dev), or generated per tenant on first use
Control planethe reserved tenant <customer>:<product>:<env>:superadmin, one per product and environment

One process serves any product. Only iam — the service’s own identity — is registered at boot; every other product name is registered on first sighting of it in X-Product.

  • Authentication — realms, providers, and every login flow that ships
  • Tokens and sessions — claims, key custody, refresh rotation, managed sessions
  • Authorization — roles, access rules, API-key capabilities, the operator plane
  • Identity — the identity entities and their CRUD surface
  • Operating — boot config, bootstrap, tenant onboarding, failure modes
  • HTTP API reference — every route, request and response
  • Errors — the external error-code table