Skip to content

Latest commit

History

137 Commits

Folders and files

NameName
Last commit message
Last commit date
RustyAuth — a copper and graphite R with an integrated passkey aperture

Built in Rust. Built on SableDB. Built for passkeys.

A small, self-hosted Rust identity service for WebAuthn ceremonies, durable browser sessions and short-lived ES256 access tokens.

License: Apache-2.0Rust: 1.94.1+Status: 1.0.0 GAStorage: SableDB

Website · Developer docs · Repository docs · Source

Deploy on Railway

Important

RustyAuth 1.0.0 is generally available for the Rust server, container deployment topologies and Dioxus web dashboard. Desktop, iOS and Android applications remain preview-only and are outside the 1.0.0 support contract. Tagged and registry artifacts are published only by the evidence-gated release workflow; pin an exact released version or image digest in production.

What RustyAuth is

RustyAuth is passkey-first authentication built in Rust on SableDB. It is a reusable authentication boundary for applications that want passkeys without handing their identity data to a hosted identity provider. The browser talks to a narrow Rust/Axum service; that service stores durable identity state in a private SableDB instance and issues short-lived JWTs for downstream APIs.

RustyAuth is not a user-interface framework, authorization engine or general OpenID Provider. It authenticates an identity and produces claims. Your application remains responsible for roles, permissions, entitlements and resource ownership.

Implemented today

  • WebAuthn passkey registration and authentication with five-minute, server-side, single-use ceremonies;
  • persistent users, passkeys and sessions in SableDB using its Valkey-compatible protocol;
  • multiple canonical email/phone identifiers and optional given, family and display names per stable account UUID;
  • HttpOnly, SameSite=Strict sessions with idle and absolute expiry;
  • multiple passkeys per account, labels, last-used timestamps and final-credential protection;
  • passkey revocation that immediately ends the sessions that passkey created;
  • recent-authentication enforcement before credential removal;
  • passkey sign-counter regression detection;
  • ES256 JWT issuance with issuer, audience, tenant, subject, session and authentication-method claims;
  • OpenID-style discovery and a public JWKS endpoint;
  • ordered, cursor-based authentication-event polling and gRPC streaming;
  • private Connect/gRPC identity reads, exact search and controlled mutations;
  • a separately deployable Dioxus web dashboard with passkey registration, authentication, server-side sign-out, binary Connect/Protobuf and responsive layouts, plus shared preview-only desktop/mobile feature builds;
  • a durable Fleet control-plane slice for organizations, projects, environments, scoped role bindings, central audit, realm discovery and single-use pairing without direct access to realm databases;
  • durable single-organization settings and role-gated operator access;
  • scoped service accounts with independently revocable, one-time credentials and short-lived ES256 token exchange;
  • exact-origin CORS and request-origin enforcement;
  • browser response hardening with CSP, frame denial, cross-origin isolation, a restrictive permissions policy and production HSTS;
  • bounded request duration, request body size and shutdown grace;
  • liveness, dependency readiness, request IDs and structured JSON logging;
  • development-only, one-use agent browser handoffs for an existing account;
  • automatic staged signing-key rotation with overlapping JWKS publication; and
  • scheduled, authenticated logical backups to S3-compatible storage with verification and clean-room restore commands.

See Project status for functionality that is deliberately not claimed yet.

Architecture

flowchart LR
Browser["Browser / relying party"] -->|"WebAuthn + binary Connect"| Dashboard["Dioxus dashboard\nstateless gateway"]
Dashboard -->|"HttpOnly session"| Auth["RustyAuth API\nRust + Axum"]
Auth -->|"private Valkey protocol"| Sable["SableDB\nidentity state"]
Auth -->|"short-lived ES256 JWT"| API["Application API / SpacetimeDB"]
API -->|"JWKS verification"| Auth
Auth -.->|"scheduled AES-256-GCM\nlogical backups"| Bucket["S3-compatible bucket"]
Loading

Only the stateless dashboard gateway is public in the operator topology. The Rust API and SableDB remain private; SableDB is a persistence engine, not the authorization boundary. A downstream service must verify the JWT signature, iss, aud, exp, tenant_id and the claims relevant to its own policy.

Read Architecture for the trust boundaries, data model and complete flows.

Quick start

Requirements: Docker with Compose, OpenSSL and curl.

git clone --recurse-submodules https://github.com/rusty-auth/rustyauth.git
cd rustyauth
scripts/local-stack standalone up

If the repository was cloned without submodules, run git submodule update --init --recursive once before building the bundled SableDB image. RustyAuth records the exact reviewed revision of its public rusty-auth/sabledb fork at vendor/sabledb.

The standalone command creates private secrets in the ignored .env.standalone.local, then starts the separate Dioxus dashboard, realm backend and private SableDB. It also derives an ignored local configuration from rustyauth.example.yaml, keeping the public origin aligned when the local port is overridden. Open http://localhost:8081. Use the first-run setup screen and the generated bootstrap token to create the allowlisted local owner passkey, or use ?preview=1 without mutating SableDB.

If a port is occupied, set STANDALONE_DASHBOARD_PORT or FLEET_DASHBOARD_PORT before running the launcher; the local issuer and WebAuthn origin follow the selected port automatically.

To run the central Fleet topology on http://localhost:5196 instead:

scripts/local-stack fleet up
curl --fail http://127.0.0.1:8081/healthz
curl --fail http://127.0.0.1:8081/readyz
curl --fail http://127.0.0.1:8081/.well-known/passkey-auth

Stop the containers without deleting identity data:

scripts/local-stack standalone down

The sabledb_data volume survives container replacement. Add --volumes only when you intentionally want to erase the local identity store.

Important

The checked-in YAML contains only non-secret policy. No secret ships with a value: .env.example leaves every secret blank and compose.yaml refuses to substitute a default, so an unpopulated .env stops the stack by name rather than starting on something readable in this repository. Generate each secret independently, including for local work. RustyAuth additionally refuses a 32-byte key whose bytes are all identical, which catches an unedited 0000… placeholder — but that check cannot tell a generated key from a published one, which is why there are no defaults to inherit.

Configuration as code

Each RustyAuth container can load one versioned YAML document describing its issuer, WebAuthn relying party, private service endpoint, token and session lifetimes, signing-key lifecycle, operator bootstrap allowlist and backup policy. Realm documents can also declare webhook destinations as deployment-owned desired state. Secrets remain separate environment variables or Docker secret files.

rustyauth config example realm > rustyauth.yaml
rustyauth config validate rustyauth.yaml
rustyauth --config rustyauth.yaml

Containers automatically read /etc/rustyauth/config.yaml. Platforms without file mounts, including Railway, can supply the identical document as the multiline RUSTYAUTH_CONFIG_YAML variable. Existing environment-only deployments continue to work. See Configuration for precedence, secret inputs, Compose, Railway, webhook ownership and the production backup example. Values declared as IaC remain deployment-owned: the dashboard identifies them as managed by YAML instead of presenting a second writer.

Backup, key and operator operations

The same binary provides a small operator CLI. When backup storage is configured, the service creates a verified logical backup after startup and every six hours by default; signing-key maintenance is automatic.

rustyauth doctor
rustyauth backup create
rustyauth backup list
rustyauth backup status
rustyauth backup verify <object-key>
rustyauth keys status
rustyauth keys rotate
rustyauth operator list
rustyauth operator find <email>
rustyauth operator promote <user-id><owner|administrator|support|auditor>
rustyauth operator demote <user-id>

operator promote is the supported way to create the first Owner. Dashboard bootstrap requires an operator email the account has already verified, and verifying an identifier is itself an operator action — so on a fresh deployment neither can happen first. The CLI breaks that cycle, and deliberately costs privileged container-command access to the deployment (the production image has no shell) rather than control of an inbox. Setting AUTH_OPERATOR_EMAILS alone is no longer enough to become an operator.

Run restore as an offline operation against an empty RustyAuth namespace:

rustyauth backup restore <object-key>
rustyauth doctor

Restore invalidates sessions by default, creates fresh signing-key material and fails closed if its final security steps do not complete. --preserve-sessions exists only for an explicitly reviewed incident response. See Configuration for key-overlap settings and Backups and disaster recovery for the format, complete recovery boundary and clean-room runbook.

How integration works

Runnable end-to-end material lives in examples/: a static relying party exercising the full ceremony flow and a Node service verifying issued tokens against JWKS. The @rustyauth/client package wraps the browser side of these flows, including the WebAuthn JSON encoding.

Registration

  1. A production operator issues an identifier-bound, one-time invitation; local development may instead use x-bootstrap-token.
  2. RustyAuth creates WebAuthn registration options and stores the ceremony for five minutes.
  3. The browser calls navigator.credentials.create() and returns the credential.
  4. RustyAuth atomically consumes the ceremony, verifies the credential, creates the user and starts a session.

The bootstrap token is a development administrative credential and production registration rejects it. Production invitation codes are returned once, stored only as digests and consumed atomically with account creation.

Sign-in and token exchange

  1. The browser requests authentication options for a canonical email address or E.164 phone number.
  2. RustyAuth stores a single-use ceremony and returns WebAuthn options.
  3. The browser calls navigator.credentials.get() and returns the assertion.
  4. RustyAuth verifies user presence and verification, advances credential state and creates an HttpOnly session.
  5. The browser calls POST /v1/token; RustyAuth returns a short-lived ES256 access token while the durable session remains in its cookie.
  6. The downstream service verifies that token against /.well-known/jwks.json and applies its own authorization.

The access token is returned in JSON and should be held in memory, not local storage. Session tokens are stored only as SHA-256-derived SableDB keys; the raw bearer value lives in the HttpOnly cookie.

What one realm can serve

RustyAuth publishes capacity only with the exact dataset, workload, resource ceiling, immutable images and retained evidence. The retained Railway enterprise run contains 10,000 registered accounts and 10,000 valid sessions. A 1 vCPU / 1 GB API with a 4 vCPU / 4 GB SableDB sustained 2,400 mixed authenticated requests per second for an exact two-minute window at 173.3 ms public-edge p95 and 0.0326% request failures, with zero server 5xx responses. At 3,200 RPS it missed scheduled work and crossed the strict internal latency gates. However, the planned 1,680 RPS soak was stopped after 37 minutes 40.7 seconds and never ran recovery; its partial Railway edge p95 was 548 ms. The 2,400 RPS boundary is retained as informational evidence, while 1,680 RPS remains an unqualified headroom calculation rather than a published production operating rate.

“50,000 users” can mean stored accounts, monthly active users or people making requests simultaneously. The current dataset does not qualify 50,000 or 100,000 stored accounts yet. Applying the disclosed typical activity model of six authenticated requests per user per minute to the candidate 1,680 RPS calculation gives 16,800 simultaneously active users and approximately three or six equivalent cells for 50,000 or 100,000 continuously active users. Those figures are planning projections, not support commitments. Until a fresh one-hour soak and recovery pass, supported planning must use the earlier published 800 RPS baseline and its 560 RPS headroom rate, alongside a separately qualified stored-account tier.

See Benchmarks and single-realm capacity for the provider comparison, Railway cost model, realm-cell scaling rules and the reviewed public results.

Public endpoints

EndpointAccessPurpose
GET /healthzPublicProcess liveness
GET /readyzPublicSableDB-backed readiness
GET /.well-known/passkey-authPublicRuntime capabilities
GET /.well-known/openid-configurationPublicIssuer and token metadata
GET /.well-known/jwks.jsonPublicActive, staged and overlapping ES256 public keys
POST /v1/passkeys/registration/optionsOrigin + bootstrapStart initial registration
POST /v1/passkeys/registration/verifyOrigin + bootstrapFinish initial registration
POST /v1/passkeys/authentication/optionsOriginStart passkey sign-in
POST /v1/passkeys/authentication/verifyOriginFinish passkey sign-in
POST /v1/tokenSession + originMint a short-lived access token
POST /v1/sign-outOriginRevoke the current session
GET /v1/accountSession + originRead profile and email/phone identifiers
POST /v1/account/profilePasskey session + originReplace given, family and display names
POST /v1/account/identifiers*Recent passkey + originAdd, remove or select a primary identifier
GET /v1/credentialsSession + originList account passkeys
POST /v1/passkeys/registration/add/*Recent passkey + originAdd another passkey
POST /v1/credentials/renamePasskey session + originRename a passkey
POST /v1/credentials/revokeRecent passkey + originRemove a non-final passkey
GET /v1/events?after=NBootstrapPoll up to 500 ordered events
POST /v1/email-linksOriginAppend a privacy-preserving sign-in request event; no token or email by design

The complete HTTP and private RPC contracts are documented in API. The private rustyauth.identity.v1 service reads/searches profile, identifier and passkey metadata and applies controlled identity mutations. rustyauth.organization.v1 and rustyauth.service_accounts.v1 are authorized by passkey operator sessions; service-account credential exchange returns scoped, short-lived JWTs. rustyauth.events.v1 provides resumable server streaming. All services support Connect, gRPC-Web and gRPC. The 1.x contract uses semantic versioning; pin a release or image digest and review the changelog before upgrading.

Configuration and deployment

RustyAuth validates all required configuration at startup and fails closed on an unset AUTH_ENV, placeholder encryption keys with no entropy, partial backup configuration, invalid origins, weak production bootstrap tokens and plaintext production SableDB addresses outside private networking.

The intended realm topology is a public Dioxus dashboard, a private RustyAuth backend and a private persistent SableDB, with an optional private S3-compatible bucket. A separate Fleet project uses the same three-way separation for its dashboard, control-plane API and central datastore. Railway is the first packaging target, not a runtime dependency.

Project status

RustyAuth is at 1.0.0. The supported GA scope is the Rust server and control plane, the supplied container topologies, and the Dioxus web dashboard. Native desktop and mobile clients remain explicit previews.

CapabilityStatus
Passkey registration and authenticationImplemented
Multiple email/phone identifiers and basic account profilesImplemented; external verification delivery required
Durable sessions and credential managementImplemented
ES256 JWT, JWKS and automatic rotationImplemented with prepublication and retired-key overlap
Ordered HTTP event polling and gRPC event streamingImplemented
Private identity gRPC reads, exact search and mutationsImplemented
Passkey-authenticated operator dashboardImplemented; first owner created with operator promote, browser bootstrap requires a verified allowlisted email
Organization and operator control planeImplemented for one organization per instance
Service accounts and credential exchangeImplemented with scoped, one-time credentials
Email sign-in and verification deliverySign-in-link delivery remains event-only; one-time email/phone verification challenges are delivered through exact signed-webhook subscriptions
Account recoveryImplemented with one-use recovery codes, passkey re-enrolment, session revocation and audit events
Scheduled encrypted logical backupsImplemented with authenticated v3 envelopes, WORM/SSE posture verification, leases, health and read-after-write verification
Snapshot restoreImplemented for an empty target with clean-room validation, key recovery/rotation and sessions invalidated by default
Webhook event deliveryImplemented on current main with encrypted HMAC secrets, durable history, bounded retry, replay and per-destination cursors
Webhook and standalone metrics control planeImplemented on current main; local five-minute analytics projection backs bounded per-realm metrics
Multi-tenant runtime isolationClaims/events are tenant-tagged; one configured tenant per instance
Fleet management across isolated deploymentsImplemented hierarchy/RBAC, public and outbound pairing, step-up remote administration, source-tagged partial operations, rotation/revocation and dual audit
Federated Fleet Analytics V1GA feature set: trusted export/ingestion, private canonical and materialized serving, delegated API/Dioxus, residency policy and signed Parquet recovery
Dioxus operator consoleWeb is the supported 1.0.0 client; shared desktop/mobile builds, device tokens and OS-vault adapters remain preview-only
Continuous security and scale assuranceAutomated on main and scheduled runs; independent assessment and extended canary evidence remain ongoing

The 1.0.0 source and support contract is GA. Artifact publication remains fail-closed: the v1.0.0 tag is created only after the machine-readable evidence record passes, then the workflow publishes and verifies the server, control-plane, dashboard and SableDB images plus the TypeScript and Protobuf packages. Extended scale, canary and independent security work continues as asynchronous assurance on supported releases. Desktop, iOS and Android distribution is separately gated post-1.0 work. See Security hardening, the roadmap and the 1.0.0 release-readiness record.

The roadmap keeps the single-tenant foundation intact while delivering the Fleet control plane as a separate management plane for isolated deployments.

Development

Rust 1.94.1 is the minimum declared toolchain. The container currently builds with Rust 1.97.

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo build --locked --release

Tests cover configuration validation, browser-handoff confinement, credential input validation, signing-key lifecycle, authenticated backup envelopes, protocol skew/fuzzing and pinned-service fault/recovery drills. CI also performs a clean-room recovery drill against real SableDB and S3-compatible MinIO services. Extended browser/authenticator, published-artifact, organization-canary and independent-assessment exercises continue through release qualification and scheduled assurance. Native distribution remains preview-only post-1.0 work.

See CONTRIBUTING.md for the development workflow and security-sensitive change requirements.

The marketing website is an Astro workspace managed with Deno. The product dashboard is Dioxus/Rust:

deno install
deno task site:dev
deno task site:test
deno task console:check
deno task console:check:desktop
deno task console:check:mobile
deno task console:build:web

ConnectRPC contracts live in proto/ and packages/protocol; the public HTTP client lives in packages/client. The retired Solid product adapter is absent from product sources and release artifacts. Regenerate and verify the active contracts with:

deno task gen
deno task connect:check
deno task connect:test

Cloudflare Pages and rustyauth.dev are managed in infra/cloudflare with Pulumi. Production credentials are injected from the maintainers' self-hosted Infisical environment and are never committed or stored in plain Pulumi configuration.

Documentation

Start at the documentation index. It separates guided journeys from normative contracts and records which document must change with each API, schema, configuration or deployment change.

Learn and integrate

Deploy and operate

Build Fleet

Contribute

Brand and independence

Write the product name as RustyAuth with no space. The primary strapline is:

Built in Rust. Built on SableDB. Built for passkeys.

RustyAuth is an independent project. It is not sponsored, endorsed or maintained by the Rust Foundation, the Rust Project, SableDB or their contributors. It does not use the Rust cog, Cargo or Ferris artwork. See Brand and Trademarks.

Licence

RustyAuth is copyright 2026 Livermore Ledger Ltd and licensed under the Apache License 2.0. Contributions submitted for inclusion are licensed on the same terms unless explicitly agreed otherwise.

RustyAuth builds and distributes SableDB as a separate container under SableDB's BSD three-clause licence. The upstream notice and disclaimer are reproduced in THIRD_PARTY_NOTICES.md and copied into the SableDB image.

The Apache licence covers the source code; it does not grant rights to the RustyAuth name or logos. See NOTICE and TRADEMARKS.md.

About

Passkey-first authentication built in Rust on SableDB.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

80 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages