v0.2.0 assay-engine becomes a full Ory replacement: passkey, OIDC provider, biscuit, Zanzibar — all in one ~9 MB static binary.

Auth & IdP overview

assay-engine v0.2.0 ships a complete identity provider in the same binary that runs the Temporal-replacement workflow engine. One process. PG18 or SQLite. Ory + Hydra + Keto in ~9 MB.

What's in v0.2.0

Every primitive a serious self-hosted IdP needs, packaged into one Rust crate (assay-auth) and composed into assay-engine:

ModuleReplacesWhat it does
sessionOry Kratos (sessions)Cookie + CSRF + 30-day TTL session manager
passwordOry Kratos (passwords)Argon2id PHC strings, peppered hashing
jwtHydra (JWT)RS256 issue/verify with rotated JWKS (active + history)
oidc (client)Kratos (federation)Log in via Google / Apple / GitHub / any OIDC IdP
oidc_providerOry HydraFull /authorize, /token, /userinfo, RFC 7009 revoke, RFC 7662 introspect, back-channel logout
passkeyKratos (WebAuthn)webauthn-rs-backed passkey register + auth ceremonies
zanzibarOry Keto / SpiceDBReBAC tuples + recursive-CTE walk on PG18 + SQLite
biscuit(Ory has nothing)Datalog-attenuable capability tokens — always-on
adminOry Console (HTTP API)Cross-cutting users / sessions / Zanzibar / keys / audit endpoints

Why ditch Ory for assay-engine?

Storage model

All auth tables live in the auth schema (PG) or attached auth database (SQLite, default ./data/auth.db). Migrations are idempotent and version-tracked in the shared engine.migrations table:

TablePurpose
auth.usersAuthoritative user records
auth.sessionsOpaque session ids + CSRF tokens + expiry
auth.passkeysWebAuthn credentials per user
auth.user_upstreamFederated identity links
auth.auditAppend-only compliance log
auth.jwks_keysRotated JWT signing keys (active + history)
auth.biscuit_root_keysBiscuit capability-token root keys
auth.zanzibar_tuplesReBAC relation tuples
auth.zanzibar_namespacesPer-namespace schema definitions
auth.oidc_clientsRegistered consumer apps (engine = OP)
auth.upstream_providersFederated identity providers
auth.oidc_authorization_codesSingle-use codes from /authorize
auth.oidc_refresh_tokensSHA-256-hashed long-lived bearer tokens
auth.oidc_sessionsSSO session registry (one per id_token)
auth.oidc_consentsPer-(user, client) consent grants
auth.oidc_upstream_statesShort-lived federation state

Minimum viable config

# engine.toml
auto_enable_modules = ["auth"]

[server]
bind_addr = "0.0.0.0:3000"
public_url = "https://auth.example.com"   # OIDC iss + passkey origin

[backend]
type = "postgres"
url = "postgres://postgres:postgres@localhost/assay"
# Or: type = "sqlite", data_dir = "./data"

[auth]
issuer = "https://auth.example.com/auth"
admin_api_keys = ["sk_admin_replace_me"]

[auth.passkey]
rp_id = "auth.example.com"
rp_name = "Acme Identity"

[auth.oidc_provider]
enabled = true
assay-engine serve --config engine.toml
#   /auth/console                          → admin SPA
#   /.well-known/openid-configuration      → OIDC discovery (Hydra-equivalent)
#   /auth/login, /auth/passkey/*           → user-facing auth flows
#   /auth/admin/auth/*                     → admin HTTP API (api-key gated)

Dashboard panes

When the auth module is enabled in engine.modules, the engine's dashboard SPA lights up with these panes:

Where to next?