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
| Option | Type | Default | Description |
|---|---|---|---|
enable_session_listing | bool | true | Allow GET /list-sessions |
enable_session_revocation | bool | true | Allow revocation endpoints |
require_authentication | bool | true | Require 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.signatureEnable 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
| Method | Path | Description |
|---|---|---|
GET | /get-session | Return the current session and user |
POST | /get-session | Refresh an eligible session; requires session.defer_session_refresh, otherwise 405 |
POST | /update-session | Update configured writable session fields |
POST | /sign-out | Revoke the current session and clear cookies |
GET | /list-sessions | List all active sessions for the current user |
POST | /revoke-session | Revoke one specific session by token |
POST | /revoke-sessions | Revoke all sessions for the current user |
POST | /revoke-other-sessions | Revoke 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
- A session is created after successful authentication.
- The opaque token and session metadata are stored in the database.
- The token is returned to the client and usually set as a cookie.
- Authenticated requests resolve the token through a valid cookie cache or the database.
- 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 belowexpires_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-sessionreportsneedsRefreshwithout updating the stored expiry.POST /get-sessionperforms 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.
Cookie 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, andSameSiteenabled. - Session records can retain audit metadata such as IP address and user-agent.
- Users can hold multiple sessions across devices and revoke them selectively.