assay-auth 0.2.2

Authentication, OIDC (client + provider), passkey, Argon2, JWT, Biscuit capability tokens, session management, and Zanzibar-style authorization for assay-engine.
Documentation

Auth layer for assay-engine — a self-hosted, single-binary Ory replacement for assay-engine v0.2.0.

assay-auth packages every primitive a serious identity provider needs into one crate that composes into [crate::AuthCtx] and is mounted under /auth by the engine:

Module Replaces Purpose
[session] Ory Kratos (sessions) Cookie + CSRF session manager (Argon2id-backed)
[password] Ory Kratos (passwords) Argon2id PHC strings, peppered hashing
[jwt] Hydra (JWT) RS256 issue/verify with rotated JWKS
[oidc] Kratos (federation) OIDC client — log in via Google/Apple/GitHub/upstream
[oidc_provider] Ory Hydra Full OIDC provider/authorize, /token, /userinfo, /.well-known/*, RFC 7009 revoke, RFC 7662 introspect, back-channel logout
[passkey] Kratos (WebAuthn) webauthn-rs-backed passkey register + auth ceremonies
[zanzibar] Ory Keto / SpiceDB ReBAC tuples + recursive-CTE walk on PG18 + SQLite
[biscuit] (Ory has nothing) Datalog-attenuable capability tokens — always-on
[store] UserStore / SessionStore traits + PG / SQLite backends
[admin] Ory Console (HTTP API) Cross-cutting admin endpoints (users, sessions, Zanzibar, …)

Why use assay-auth instead of Ory?

  • One static binary (assay-engine, ~9 MB stripped) replaces a stack of Kratos + Hydra + Keto + Oathkeeper containers. Same features, ~50× less RAM and one process to ship/log/restart.
  • Backend symmetry. PG18 + SQLite are both first-class via feature flags. SQLite means a self-hosted single-tenant deployment needs no database server at all — unique vs Ory.
  • Biscuit out of the box. Datalog-attenuable capability tokens that callers can scope down further without a server round-trip. Ory has nothing equivalent; this is a real differentiator.
  • Workflow + auth share storage. Atomic transactions across auth.usersworkflow.workflows (cross-schema FKs on PG, both attachments on SQLite) — signups can mint workflow records in one transaction. Splitting Ory + Temporal forces 2-phase commit.
  • Lua-scriptable. Every auth surface is reachable from the assay.auth Lua stdlib module — operators can build login, admin, and federation flows in scripts that the runtime binary ships with.

Getting started

Compose AuthCtx into your axum state via axum::extract::FromRef (the engine binary's EngineState<S> is the canonical recipe — see [assay_engine] for the wiring). Out-of-the-box you'll need a [store::UserStore] + [store::SessionStore]; the [store::PostgresUserStore] / [store::SqliteUserStore] / [store::PostgresSessionStore] / [store::SqliteSessionStore] impls cover both backends.

# async fn build() -> anyhow::Result<()> {
# let pool = sqlx::SqlitePool::connect("sqlite::memory:").await?;
use std::sync::Arc;
use assay_auth::AuthCtx;
use assay_auth::store::{SqliteUserStore, SqliteSessionStore};

let users = SqliteUserStore::new(pool.clone()).into_dyn();
let sessions = SqliteSessionStore::new(pool.clone()).into_dyn();
let ctx = AuthCtx::new(users, sessions);
// ctx is now ready to be plugged into your Router via FromRef.
# Ok(()) }

For the full deployment shape (issuer, JWKS rotation, OIDC provider discovery, biscuit root key bootstrap, passkey RP setup, Zanzibar store) lean on assay_engine::run — it builds an AuthCtx from engine.toml, runs the auth migration, and serves everything on one port.

Storage model

All auth tables live in the auth schema (PG) or attached auth database (SQLite, default ./data/auth.db). The migration runner ([schema::migrate_postgres] / [schema::migrate_sqlite]) records each applied version in engine.migrations under module = 'auth', keyed by [MIGRATION_VERSION]. Migrations are idempotent — every CREATE uses IF NOT EXISTS.

Feature flags

The default feature auth pulls in every module. Slim builds can opt a la carte — see the per-module #[cfg(feature = "...")] gates below. backend-postgres and backend-sqlite are independent and both default-on; downstream binaries pick the one(s) they need.

Phase trail

Module boundaries and per-module rationale live in plan 11. v0.2.0 alignment (Ory-replacement scope, biscuit-built-in posture, schema layout) lives in plan 12c §"v0.2.0 alignment".