Better Auth in Rust

Sessions

Session lifecycle, transport, refresh behavior, and session-management endpoints.

The SessionManagementPlugin provides endpoints for querying, listing, and revoking sessions. Sessions are created automatically during sign-up, sign-in, passkey authentication, and successful 2FA completion.

Setup

These examples assume you've already defined AppAuthSchema. If you want a complete setup from entity definitions through Axum mounting, start with the Axum integration guide.

use better_auth::plugins::SessionManagementPlugin;

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .plugin(SessionManagementPlugin::new())
    .build()
    .await?;

Plugin Options

OptionTypeDefaultDescription
enable_session_listingbooltrueAllow GET /list-sessions
enable_session_revocationbooltrueAllow revocation endpoints
require_authenticationbooltrueRequire a valid session for plugin endpoints

Opaque Session Tokens

Better Auth stores opaque session tokens in the database. Browser cookies contain the token and an HMAC-SHA256 signature:

Cookie: better-auth.session_token=session_abc123.signature

Enable Bearer transport explicitly for mobile apps or API clients:

use better_auth::config::BearerConfig;

config.session.bearer = Some(BearerConfig::default());

Bearer transport accepts the raw token returned by sign-in or the signed token from set-auth-token. Set require_signature: true to accept only signed Bearer tokens. An accepted Bearer token takes precedence over the session cookie.

Endpoints

MethodPathDescription
GET/get-sessionReturn the current session and user
POST/get-sessionRefresh an eligible session; requires session.defer_session_refresh, otherwise 405
POST/update-sessionUpdate configured writable session fields
POST/sign-outRevoke the current session and clear cookies
GET/list-sessionsList all active sessions for the current user
POST/revoke-sessionRevoke one specific session by token
POST/revoke-sessionsRevoke all sessions for the current user
POST/revoke-other-sessionsRevoke every session except the current one

Session lists preserve the storage adapter's result order, matching upstream. Clients that need a specific order should sort the returned sessions.

Session Lifecycle

  1. A session is created after successful authentication.
  2. The opaque token and session metadata are stored in the database.
  3. The token is returned to the client and usually set as a cookie.
  4. Authenticated requests resolve the token through a valid cookie cache or the database.
  5. Database reads reject expired or revoked sessions. A valid cookie cache remains usable until its cache or session expiry.

Refresh Behavior

Session refresh is controlled by SessionConfig:

use better_auth::AuthConfig;
use better_auth::config::SameSite;
use chrono::Duration;

let mut config = AuthConfig::new("secret...")
    .base_url("https://auth.example.com");
config.session.expires_in = Duration::days(7);
config.session.update_age = Some(Duration::days(1));
config.session.disable_session_refresh = false;
config.session.defer_session_refresh = false;
config.session.fresh_age = Some(Duration::minutes(10));
config.session.cookie_name = "better-auth.session_token".to_string();
config.session.cookie_secure = true;
config.session.cookie_http_only = true;
config.session.cookie_same_site = SameSite::Lax;
  • update_age = Some(duration): refresh when the remaining lifetime falls below expires_in - duration.
  • update_age = None: each database read can refresh the session.
  • disable_session_refresh = true: never refresh automatically.
  • defer_session_refresh = true: GET /get-session reports needsRefresh without updating the stored expiry. POST /get-session performs the refresh.
  • fresh_age: optional window for treating a session as "fresh" for sensitive actions.

A disableRefresh=true query parameter prevents refresh for the request. Sessions created with rememberMe: false expire in the database after 24 hours. These sessions use browser-session cookies and a signed dont_remember marker; requests with this marker do not refresh the session automatically. Remembered sessions use the configured expires_in lifetime. /get-session responses include Cache-Control: no-store and Pragma: no-cache.

Enable a signed or encrypted session cache to avoid a database read on each request:

use better_auth::config::{CookieCacheConfig, CookieCacheStrategy};

config.session.cookie_cache = Some(CookieCacheConfig {
    enabled: true,
    max_age: Duration::minutes(5),
    strategy: CookieCacheStrategy::Jwe,
    version: "1".into(),
});

Compact uses HMAC-SHA256, Jwt uses HS256, and Jwe uses A256CBC-HS512 encryption. Each cache is bound to its signed session token and configuration version. Large values use numbered cookie chunks. Authentication writes the cache, and sign-out clears its chunks.

Use disableCookieCache=true to read the database immediately. Session listing and revocation also require a database read. Changing version invalidates previously issued caches. This configuration uses a persistent session store; stateless cache refresh is not exposed.

Using Sessions in Axum Handlers

With the axum feature, use the CurrentSession extractor to access the authenticated user directly in your handler:

use axum::Json;
use better_auth::integrations::axum::CurrentSession;
use better_auth::prelude::{AuthSession, AuthUser};

async fn profile(session: CurrentSession<AppAuthSchema>) -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "user_id": session.user.id(),
        "session_token": session.session.token(),
    }))
}

Use OptionalSession<AppAuthSchema> for routes that should work for both authenticated and anonymous users.

Application Session Fields

POST /update-session updates configured application fields on the authenticated session. Unknown fields and built-in credentials are not writable. An empty update returns 400. Fields with input: false reject truthy input; returned: false excludes a field from session responses.

The request body must be a JSON object with Content-Type: application/json. Body validation runs before session authentication. A body with a missing or unsupported media type returns 415.

Add columns to the application-owned session entity and migrate the database before configuring those fields. AuthEntity generates the update mapping and retains database defaults for new sessions.

use better_auth::config::SessionFieldConfig;

config.session.additional_fields.insert("label".into(), SessionFieldConfig {
    field_name: Some("display_label".into()),
    ..Default::default()
});

field_name identifies the field in the model's serialized representation. The public field name is the map key. For manually implemented SeaOrmSessionModel types, implement apply_fields to persist the configured fields.

Two-Factor Interaction

When 2FA is enabled, sign-in verifies the primary credential first and then pauses on a pending verification flow. A full session is only created after verify-totp, verify-otp, or verify-backup-code succeeds.

Security Notes

  • Session tokens are generated with OsRng.
  • Cookie transport should normally keep Secure, HttpOnly, and SameSite enabled.
  • Session records can retain audit metadata such as IP address and user-agent.
  • Users can hold multiple sessions across devices and revoke them selectively.

On this page