Better Auth RS
Authentication for Rust with Axum integration and application-owned SeaORM entities. The compatibility target is better-auth@1.7.6: routes, payloads, cookies, and errors follow the upstream TypeScript runtime.
[!WARNING] Version
1.0.0-alpha.3is in development. Public Rust APIs and database schemas can change between alpha releases.
Configure model IDs and application-owned defaults through database ID generation. User creation evaluates the ID generator at the ID field's schema position among input callbacks.
Custom adapters must provide a credential-account point query independent of list pagination; see adapter configuration. Enable advanced.database.joins = Some(true) for typed core and Organization associations with Memory child references or SeaORM query snapshots. Plugins can declare custom models for reference resolution through runtime model registration; custom persistence remains the application adapter's responsibility.
Sessions use signed cookies. Cookie attributes follow upstream serialization and mutation order; see the cookie guide for partitioned chunks and LastLogin prefix behavior. Response headers preserve repeated cookie writes in order; explicit expiration removes earlier values and chunks. Enable SessionConfig.bearer explicitly for Authorization header authentication; see the session guide for cache and application field configuration. Use Custom Session to transform public session responses with typed application context. Use Multi Session to remember and switch between accounts, with adapter-specific session selection and joined field projection.
Endpoint responses and hook contexts retain native fields until a JSON or byte boundary. Read AuthResponse.body with field_value(), json(), or bytes(); hook and endpoint context bodies use FieldValue. See response hooks for native values and replacement behavior.
Use BetterAuth::stateless(config) without an application schema or database. Encrypted session and OAuth cookies survive adapter restart; process-local users and plugin records do not. See stateless sessions for storage defaults, hooks, and revocation behavior.
Configure application user fields with AuthConfig.user.fields_mut() and matching application-owned entity columns. Configured fields use their logical name when field_name is omitted or empty, including username projections. User lists filter and sort declared scalar display fields by logical or mapped name before output transforms, including nullable String, Boolean, and Number filters. SeaORM reads non-reference String query values from model columns; SQLite also reads declared Boolean and Number additional columns. SQLite additional-field output also reads supported non-reference column values before callbacks and decoding, preserving null and empty values despite Serde omission. Undeclared filters return an error. Undeclared nonempty sort fields fail before SQL reads; Memory reports the error when at least two filtered rows require comparison. Runtime callbacks and record fields use FieldValue, FieldDate, and FieldMap; JSON conversion occurs at explicit input, cache, and output boundaries. Default and update factories return AuthResult<FieldValue> and propagate failures before storage writes. Session creation hooks receive complete native fields through FieldMap and return DatabaseHookUpdate<FieldMap>; see database hooks for patch and secondary-storage ordering. See user fields for input validation, defaults, transforms, public visibility, output coverage, Memory JSON representation, and SQL query column mappings.
API Key adapter policies support numeric names, and Passkey display policies can share one physical column; see API Key fields and Passkey fields.
Adapter-created JSON text uses JavaScript property ordering and number formatting. Custom adapters can use FieldValue to retain runtime values until an explicit JSON boundary. Manual SeaORM models can use seaorm::field_value::{from_column, decode_column} for typed column conversion. See JSON field bindings for value conversion and native SQL JSON storage.
Account and Verification Memory fields store JSON as text and retain native arrays. See field representation for callback values and reference conversion. Account/User and Session/User joins execute the selected schema relationship and preserve single, missing, or array results through better_auth::store::JoinValue. Memory User associations also preserve omitted fields instead of converting them to null. Session snapshots retain loaded relationships as SessionData<JoinValue<UserView>>; see database integration for custom adapter migration. NativeSessionData preserves native snapshots; SessionData defaults to a typed User.
User record updates preserve raw name and image values through SchemaValue. Use update_user_by_id_value for native selectors and nullable results; see User updates for cancellation and hook behavior. See database integration for physical column contracts and the SQLite parameter-binding safety boundary.
Native SQLite User timestamp writes use UTC text with millisecond precision. See the database guide for precision and custom-model limits; the paired contract verifies equal raw text and four public reads for its fixed inputs.
The schema generator keeps its default table name when modelName is omitted or empty. Fresh SQLite generation uses upstream native table and column defaults for User, Account, Verification, JWK, RateLimit, Member, OrganizationRole, Team, Invitation with teams enabled, DeviceCode, and WalletAddress. Fresh PostgreSQL and MySQL generation uses native column defaults for User, Account, Verification, JWK, RateLimit, Member, OrganizationRole, Team, Invitation, DeviceCode, and WalletAddress; bundled entities retain their existing mappings. Generated User email is required and uses a native unique constraint; its Rust input remains optional. Generated Invitation storage uses an optional role; public invitation inputs and role logic remain unchanged. Fresh Session generation uses an explicit row-presence model; --session-active-column retains the Rust active-column extension for regeneration. Use --device-code-legacy-schema to retain the previous DeviceCode representation. See schema mapping for configuration, preserved declarations, and migration boundaries.
Passkey name/aaguid and API Key name preserve omitted, null, and transformed JSON display values through SchemaValue<Option<String>>, including response decoding and API Key cache reads. These fields accept String, Enum, or Json policies and aliases to their corresponding columns. Json declarations require matching generated or application-owned JSON storage; SQLite uses SqlText, and PostgreSQL/MySQL use Json. Enum policies do not restrict strings to the declared list; see plugin fields. Cached name sorting propagates ordinary JSON object conversion errors; see API Key storage.
Generate Passkey storage with --plugins passkey for the 11 standard columns, or add --passkey-legacy-schema to retain opaque credential storage. Bind the generated AppPluginSchema; see Passkey storage modes for typed inputs and application-owned migrations.
Generate TwoFactor storage with --plugins two-factor for the seven upstream columns and nullable verification fields. Use --two-factor-legacy-schema when regenerating the previous schema. Declare application columns with twoFactor.additionalFields and register matching runtime policies; see TwoFactor storage for the public additional_fields maps.
Fresh API Key generation uses table apikey, native camelCase columns, nullable Boolean flags, INTEGER numeric columns, and nonunique credential indexes. Use --api-key-legacy-schema to retain the previous names, types, nullability, and indexes during regeneration. Runtime flags preserve NULL through SchemaValue<bool>; see schema mapping for the public type and database migration boundaries. API Key expiration configuration accepts fractional seconds for its default and fractional days for its bounds; see API Key configuration for units. Declare application columns with apikey.additionalFields and register matching runtime policies; API Key storage describes the additional_fields maps, guarded usage updates, and cache behavior.
Configure Social Twitch with OAuthProvider::twitch and TwitchOptions, Social LINE with OAuthProvider::line, or Generic LINE with GenericOAuthConfig::line and resolved inputs through GenericOAuthProfileContext; see the OAuth guide for provider-specific PKCE options, fractional token lifetimes, the alpha callback migration, and the verification boundary. Use OAuthProvider::gitlab_with_issuer for self-hosted GitLab. OAuthCallbacks supplies custom ID-token verifiers with the typed endpoint context, runtime, and resolved session. Social refresh callbacks and legacy ID-token verifiers retain optional NativeRequest metadata. Social profile fields preserve omitted and null email/verification values; callbacks preserve application errors. Verification-email delivery failures are logged without changing the verification decision. The guide describes provider-specific missing-profile responses.
Use OAuth Popup to return OAuth sign-in results to a trusted popup opener. Enable OpenAPI for the configured runtime schema and Scalar reference page, including custom JWT discovery paths, registered model presence and declaration order, JavaScript model and field-key enumeration, and storage-dependent Verification components.
The JWT plugin supports local and custom signing, server-only verification, and asymmetric session cookie caches. Database key selection follows the configured query limit.
Use CAPTCHA for request verification and Have I Been Pwned for compromised-password checks. Versioned secrets support encryption-key rotation with retained legacy data.
For integration tests, enable TestUtilsPlugin and use auth.test() for seeded users, authenticated cookies, and OTP capture. See the test utilities guide.
Cookie lifetime configuration and HTTP cookie helper parameters accept fractional seconds. Explicit CookieAttributes.expires uses chrono::DateTime<Utc>. HTTP issuance and clearing return AuthResult and enforce the 400-day limits before formatting; see cookie configuration for numeric types and override precedence. The two Two Factor cookie lifetimes use f64 seconds and preserve explicit zero. The TOTP period and account lockout duration also accept fractional seconds. TOTP counters preserve JavaScript Number window offsets and unsigned 64-bit wrapping; see the same guide for numeric and timing boundaries. Direct TwoFactorStore::record_two_factor_failure callers provide a borrowed deadline closure, evaluated after the counter reaches the threshold. OAuth Proxy accepts f64 seconds for its maximum profile age, including the upstream non-finite comparisons.
Use user.additionalFields and session.additionalFields in the CLI schema configuration to generate application columns. Register matching runtime field policies through AuthConfig.user and AuthConfig.session; see schema mapping.
Quick start
These examples use the current master branch. The DX changes are not yet published as a new crate release. Add these dependencies to your application's Cargo.toml:
[]
= { = "1.0.0-alpha.3", = "https://github.com/better-auth-rs/better-auth-rs", = "master", = ["axum", "seaorm2"] }
= "0.8"
= { = "1", = ["macros", "rt-multi-thread", "net"] }
= { = "1", = ["derive"] }
= "1"
Generate the core auth entities:
The CLI also preserves explicit core model declarations and the bound rate-limit model name for initialization telemetry. Empty native field mappings retain the model's resolved defaults and raw declarations; see schema generation.
Use --generate-id serial for integer IDs. Use --generate-id uuid --database postgres for native PostgreSQL UUIDs. The database option also selects storage for fresh application JSON fields: SQLite uses scalar-capable SqlText, while PostgreSQL/MySQL retain Json. Match the generated schema to the database backend and AuthConfig.advanced.database.generate_id. Derived plugin and organization bindings expose declared ID references so Serial writes normalize numeric aliases after configured input transforms.
Use this src/main.rs:
use ;
use ;
use AxumIntegration;
use ;
use ;
use Arc;
async
Set AUTH_SECRET to a random secret of at least 32 characters, then run cargo run. The builder resolves explicit secrets before BETTER_AUTH_SECRET and AUTH_SECRET; short secrets warn, while NODE_ENV=production rejects the default development key. The in-memory database resets when the process stops. Use application-owned versioned migrations for persistent databases; create_auth_tables only initializes an empty database.
The quick-start guide includes sign-up and cookie session requests; the check gate compiles and runs the documented application. The independent consumer verifies public schema derives, generated entities, registration, login, Axum sessions, API keys, and TOTP.
Plugins and features
Plugins include email/password, username, sessions, password management, email verification, email OTP, phone numbers, anonymous accounts, SIWE, Magic Link, one-time tokens, multiple sessions, last login method, JWT, OAuth, One Tap, OAuth Proxy, organizations, two-factor authentication, passkeys, API keys, and admin. Use UsernamePlugin for username options or EmailPasswordPlugin::username(true) for defaults. Generate fields and tables for each selected plugin; startup rejects missing entity fields. Bind generated custom plugin tables with with_plugin_schema::<AppPluginSchema>(). See database integration.
One-time token lifetimes include storage hashing time. See session transfer for expiration and consumption behavior.
Generic raw-profile handlers return AuthResult<Option<serde_json::Value>>: Some supplies a profile, None reports absence, and application errors propagate. Configure Generic OAuth or OIDC with OAuthPlugin::add_generic_provider and GenericOAuthConfig, including constructors for Auth0, Keycloak, Okta, Microsoft Entra ID, Gumroad, HubSpot, Patreon, Slack, and Yandex. OIDC discovery supplies endpoints and JWKS; set require_id_token_verification: true to require verification capability. The OAuth guide covers client authentication, profile mapping, sign-up restrictions, and email verification. Signature verification requires OpenSSL 3.0 or newer; the complete ML-DSA algorithm set requires OpenSSL 3.5 or newer.
The organization plugin supports optional teams, team membership limits, active teams, and persisted roles scoped to an organization. Enable teams through OrganizationTeamsConfig and dynamic roles through dynamic_access_control(true) with access-control statements. Use auth.organization()?.add_member(Some(body)).await for server-only member creation through the native hook pipeline.
Memory Serial mode stores numeric primary IDs and returns string IDs through public record APIs. Verification reservations retain their deterministic string IDs. Wallet, Passkey, TwoFactor, and TeamMember owner values use SchemaValue<String> and preserve numeric references in storage; use user_id.typed()? when you need a string. API Key referenceId remains an ordinary string because upstream does not declare an ID reference. Memory Serial mode applies Organization reference conversion to stored fields, queries, and internal relation keys. See Organization field policies for typed storage boundaries.
User, Session, JWK, and Wallet ID declarations retain their field position. The adapter controls their generation and conversion; see model IDs for nested callback behavior.
The device authorization plugin supports asynchronous code generators and propagates callback errors before persistence. Default device codes contain ASCII letters and digits; verification links replace any existing user-code query parameter.
The Admin plugin preserves a cancelled user update as a nullable response. Database-hook errors continue to propagate. User cache refresh errors use the instance logger and preserve the update result; see commit ordering.
HTTP rate limits support memory, database, secondary, and custom storage. Use BetterAuth::call_endpoint for trusted native endpoint calls with the registered plugin hooks.
Passwords use Better Auth's scrypt format by default. The password guide explains explicit Argon2 migration. Add HaveIBeenPwnedPlugin to reject compromised passwords before hashing.
| Cargo feature | Purpose |
|---|---|
native-tls |
Default TLS backend |
rustls |
Alternative TLS backend; disable default features |
axum |
Routes and session extractors |
seaorm2 |
SeaORM store, entity derives, and SQLite/PostgreSQL/MySQL drivers |
redis-cache |
Asynchronous Redis secondary storage for sessions, verifications, and atomic rate-limit counters |
Plugins can register adapter field policies for supported typed model fields and declared DeviceCode, JWK, WalletAddress, TwoFactor, or Passkey additional fields. Supported native display fields use their logical name when field_name is omitted or empty; see plugin field policies. Wallet create and lookup methods are also available through AuthTransaction. Memory Device create, ordinary update, and code lookups read each additional field when its output callback runs. Memory Passkey output reads name, aaguid, and declared application fields from the selected row, including changes made by earlier callbacks. Generate matching Passkey columns with passkey.additionalFields; see Passkey adapter fields for public maps and update policies. Ordinary JSON uses stored text, while reference arrays reach callbacks before public string conversion; see Device field policies for the supported Memory representations. Direct Organization store configuration also resolves built-in field order. Memory Member queries preserve equal-key ordering before pagination. Memory API Key numeric queries treat positive and negative zero as equal.
Device request fields support ordered synchronous or asynchronous validation with structured issues. See request field validation for configuration, storage separation, and the BodyValidator migration. A configured grant adds typed request authorization, declared stored fields, a required session-redemption policy, and owner-only verification context. Server-only redemption accepts DeviceCodeOwnership::Where(DeviceCodeWhere) with 11 comparison operators and two string comparison modes. Conditions use native scope, declared non-reference fields, or String, Json, and Date references to id, and retain the original code and owner bindings. In requires an array before adapter conversion. FieldEquals, FieldIn, and FieldNotIn accept String references; Serial IDs retain the shared numeric conversion. The helper returns the active internal user schema, including hidden fields. See ownership conditions for values, backend behavior, and remaining reference limitations.
Optional configuration fields use Option to distinguish omission from an explicit value. Construct social providers with a built-in constructor or OAuthProvider::custom. See configuration options for defaults and OAuth for provider configuration. Observability documents logging, tracing, opt-in telemetry, Linux CPU and memory metadata, kernel and WSL detection, process-cached Docker detection, and the current alignment boundaries.
Google sign-in maps verified ID-token claims through the shared Google verifier used by One Tap. GoogleOptions adds accepted client IDs while retaining the primary ID for authorization and token requests. See Google profile behavior for configuration, custom callbacks, and account-info behavior.
Built-in provider constructors supply provider defaults and profile mapping, including Sign in with Apple. Social Microsoft uses OAuthProvider::microsoft with MicrosoftOptions; Generic Entra keeps its separate constructor. See built-in providers for supported constructors and configuration.
Set OAuthProvider::redirect_uri to use a configured provider callback URI for both authorization and code exchange. The optional client_key follows each Social provider’s code-exchange options; see the OAuth guide for exceptions.
Custom OAuthUserInfoHandler implementations return AuthResult<Option<OAuthUserInfoResponse>> to distinguish a missing profile from an application error. Profile names, emails, and mapper overrides preserve missing, null, and string values, including GitHub email-list fallback. Sign-in normalizes omitted and null names to an empty string for storage and admission; see the OAuth guide.
Documentation and development
- Installation and Axum integration
- API key server API and database hooks
- Secondary storage for sessions, one-time verification values, and API keys
- Examples, contributing, and alignment roadmap
- Compatibility harness; upstream behavior remains the source of truth
Install devenv, then run devenv test. Local checks and CI use scripts/check.sh.
License
Licensed under MIT or Apache-2.0, at your option.
Password reset lifetime configuration uses fractional seconds (Option<f64>); see the password management guide for defaults and migration examples.