Expand description
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.users⇄workflow.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.authLua 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.
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.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”.
Re-exports§
pub use ctx::AuthCtx;pub use error::Error;pub use error::Result;pub use gate::Caller;pub use gate::CallerSource;pub use router::engine_auth_router;pub use router::oidc_spec_router;pub use router::router;pub use schema::MIGRATION_VERSION;pub use schema::MODULE_NAME;
Modules§
- admin
- Cross-cutting admin HTTP API.
- biscuit
- Biscuit capability tokens — public-key signed bearers with Datalog policy, offline verification, and caller-side attenuation.
- ctx
- Composed auth context — the value engine state holds for the auth module.
- error
- Error types for the auth crate.
- external_
jwt - External JWT issuer pass-through validation (v0.3.2). Trust JWTs
minted by an upstream OIDC provider (e.g. Hydra) without managing
engine-side users. Configured via
[[auth.external_issuers]]inengine.toml. See module-level docs for the why and how. External JWT issuer pass-through validation. - gate
- Unified authentication + authorization gate.
- jwt
- JWT issuance + verification with key rotation backed by
auth.jwks_keys. - oidc
- OIDC client — discovery, PKCE, callback, userinfo.
- oidc_
provider - OIDC provider — full Hydra-equivalent identity provider.
- passkey
- WebAuthn / passkey registration + authentication.
- password
- Argon2id password hashing wrapper.
- router
- Auth HTTP routers.
- schema
- Auth module schema bootstrap + migration runner.
- session
- Session management — opaque server-side sessions backed by
crate::store::SessionStore. - state
- Shared state types for auth router composition.
- store
- Storage traits + concrete backends for the auth crate.
- zanzibar
- Zanzibar / ReBAC layer — Keto / SpiceDB-equivalent authorization.
Functions§
- module_
name - Stable module name registered in
engine.modulesand used as the schema/attach name on both backends. Engine boot inserts a row withname = MODULE_NAMEwhen--enable=auth(or equivalent runtime signal) flips this module on.