Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rifandani/be-monorepo/llms.txt
Use this file to discover all available pages before exploring further.
Session Handling
Better Auth provides robust session management with database-backed storage, automatic expiration, and secure token handling.
Session Schema
Sessions are stored in the database with the following schema:
export const sessionTable = pgTable("session", {
id: text().primaryKey(),
expiresAt: timestamp().notNull(),
token: text().notNull().unique(),
ipAddress: text(),
userAgent: text(),
userId: text()
.notNull()
.references(() => userTable.id, { onDelete: "cascade" }),
...timestamps, // createdAt, updatedAt, deletedAt
});
export const selectSessionTableSchema = createSelectSchema(sessionTable);
export type SessionTable = z.infer<typeof selectSessionTableSchema>;
Session Fields
- id: Unique session identifier
- expiresAt: Session expiration timestamp
- token: Unique session token for authentication
- ipAddress: Client IP address (for security tracking)
- userAgent: Client user agent (browser/device info)
- userId: Reference to the user (cascade delete)
- timestamps: Created/updated/deleted timestamps
Session Middleware
The authentication context middleware extracts session data from requests:
import type { MiddlewareHandler } from "hono";
import { auth } from "@/auth/libs/index.js";
/**
* Middleware to save the session and user in context
* Sets user and session to null if not authenticated
*/
export function authContextMiddleware(): MiddlewareHandler {
return async (c, next) => {
// Get the session from the request
const session = await auth.api.getSession({
headers: c.req.raw.headers
});
// Set the user and session in the context
c.set("user", session ? session.user : null);
c.set("session", session ? session.session : null);
return next();
};
}
Usage
Apply the middleware to protected routes:
import { authContextMiddleware } from "@/routes/middlewares/auth.js";
// Apply to specific routes
app.get("/api/protected", authContextMiddleware(), async (c) => {
const user = c.get("user");
const session = c.get("session");
if (!user || !session) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({
message: "Protected data",
user,
sessionId: session.id
});
});
// Apply to route groups
const protectedRoutes = new Hono();
protectedRoutes.use("*", authContextMiddleware());
protectedRoutes.get("/profile", async (c) => {
const user = c.get("user");
return c.json({ user });
});
Authentication Context
The middleware adds authentication data to the Hono context:
// Type definitions in src/core/types/hono.ts
import type { auth } from "@/auth/libs/index.js";
interface AuthVariables {
session: typeof auth.$Infer.Session.session | null;
user: typeof auth.$Infer.Session.user | null;
}
export type Variables = RequestIdVariables &
TimingVariables &
AuthVariables;
Accessing Context
app.get("/api/example", authContextMiddleware(), async (c) => {
// Get user from context
const user = c.get("user");
// Get session from context
const session = c.get("session");
// Both are null if not authenticated
if (!user) {
return c.json({ error: "Not authenticated" }, 401);
}
// Access user properties
console.log(user.id, user.email, user.name);
// Access session properties
if (session) {
console.log(session.id, session.expiresAt, session.ipAddress);
}
return c.json({ user });
});
Session Creation
Sessions are automatically created by Better Auth during:
- User Registration: Creates session after successful sign-up
- User Login: Creates new session after authentication
- Session Refresh: Creates new session when refreshing
// Automatic session creation on login
const response = await fetch('http://localhost:3333/api/auth/sign-in/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password'
})
});
const { user, session } = await response.json();
// session.token is used for subsequent requests
Session Storage
Better Auth stores session tokens in HTTP-only cookies by default:
// Cookies are automatically set by Better Auth
// Format: better-auth.session_token=<token>
// Properties:
// - httpOnly: true
// - secure: true (in production)
// - sameSite: 'lax'
// - path: '/'
Manual Token Handling
For API clients (mobile apps, SPAs), store tokens securely:
// Client-side: Store token after login
const { session } = await loginResponse.json();
localStorage.setItem('session_token', session.token);
// Include token in requests
fetch('/api/protected', {
headers: {
'Authorization': `Bearer ${session.token}`
}
});
Session Expiry
Sessions automatically expire based on the expiresAt timestamp:
// Default: 30 days from creation
// Configured in Better Auth settings
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 30, // 30 days in seconds
updateAge: 60 * 60 * 24 * 7, // Refresh every 7 days
},
// ... other config
});
Checking Expiration
app.get("/api/session-status", authContextMiddleware(), async (c) => {
const session = c.get("session");
if (!session) {
return c.json({ authenticated: false });
}
const now = new Date();
const expiresAt = new Date(session.expiresAt);
const isExpired = now > expiresAt;
return c.json({
authenticated: !isExpired,
expiresAt: session.expiresAt,
timeRemaining: expiresAt.getTime() - now.getTime()
});
});
Session Revocation
Revoke sessions through Better Auth’s API:
// Logout (revokes current session)
app.post("/api/logout", async (c) => {
await fetch('http://localhost:3333/api/auth/sign-out', {
method: 'POST',
headers: c.req.raw.headers // Forwards session cookie
});
return c.json({ success: true });
});
// Revoke all user sessions
import { db } from "@/db/index.js";
import { sessionTable } from "@/db/schema.js";
import { eq } from "drizzle-orm";
app.post("/api/logout-all", authContextMiddleware(), async (c) => {
const user = c.get("user");
if (!user) return c.json({ error: "Unauthorized" }, 401);
await db
.delete(sessionTable)
.where(eq(sessionTable.userId, user.id));
return c.json({ success: true });
});
Session Security
IP Address Tracking
Better Auth tracks IP addresses for security:
// Configured in auth setup
advanced: {
ipAddress: {
ipAddressHeaders: Object.values(ipAddressHeaders),
},
}
// Sessions store the originating IP
// Useful for detecting suspicious activity
User Agent Tracking
User agents help identify devices:
app.get("/api/sessions", authContextMiddleware(), async (c) => {
const user = c.get("user");
if (!user) return c.json({ error: "Unauthorized" }, 401);
const sessions = await db
.select()
.from(sessionTable)
.where(eq(sessionTable.userId, user.id));
return c.json({
sessions: sessions.map(s => ({
id: s.id,
createdAt: s.createdAt,
ipAddress: s.ipAddress,
userAgent: s.userAgent,
current: s.id === c.get("session")?.id
}))
});
});
Auth Routes Handler
Better Auth handles all authentication routes:
import type { OpenAPIHono } from "@hono/zod-openapi";
import { auth } from "@/auth/libs/index.js";
export function authRoutes(
app: OpenAPIHono<{ Variables: Variables }>
) {
// Better Auth handler for all auth endpoints
app.on(["POST", "GET"], "/api/auth/**", (c) =>
auth.handler(c.req.raw)
);
}
This registers all Better Auth endpoints:
/api/auth/sign-in/email
/api/auth/sign-up/email
/api/auth/sign-out
/api/auth/session
/api/auth/verify-email
- And more…
Rate Limiting
Sessions are protected by rate limiting:
rateLimit: {
window: 15, // 15 second window
max: 150, // Max 150 requests (10 req/s)
storage: "database",
modelName: "rate_limit"
}
Rate limits are tracked in the database:
export const rateLimitTable = pgTable("rate_limit", {
id: uuid("id").defaultRandom().primaryKey(),
key: text("key").notNull().unique(),
count: integer("count").default(0).notNull(),
lastRequest: bigint("last_request", { mode: "number" }).notNull(),
});
Best Practices
- Always use middleware: Apply
authContextMiddleware() to protected routes
- Check authentication: Verify
user and session are not null
- Handle expiration: Check session expiry and refresh when needed
- Secure tokens: Use HTTP-only cookies or secure storage
- Track sessions: Monitor IP addresses and user agents for security
- Revoke on logout: Always revoke sessions on user logout
- Cascade deletes: User deletion automatically removes sessions
Next Steps