Skip to content

@mantleframework/auth

Better Auth integration for session-based authentication in Lambda handlers, with Drizzle ORM adapter, schema mapping, and permission exports for the CLI.

Setup

createAuth(db, config)

Create and cache a Better Auth instance with a Drizzle database adapter. Cached for Lambda container reuse.

typescript
import { createAuth } from '@mantleframework/auth'
import { getRequiredEnv } from '@mantleframework/env'

const auth = createAuth(db, {
  secret: getRequiredEnv('AUTH_SECRET'),
  baseURL: getRequiredEnv('AUTH_BASE_URL'),
  socialProviders: {
    apple: {
      clientId: getRequiredEnv('APPLE_CLIENT_ID'),
      clientSecret: getRequiredEnv('APPLE_CLIENT_SECRET'),
      appBundleIdentifier: 'com.example.app',
    },
    google: {
      clientId: getRequiredEnv('GOOGLE_CLIENT_ID'),
      clientSecret: getRequiredEnv('GOOGLE_CLIENT_SECRET'),
    },
  },
})

getAuth(getDb, config)

Lazily initialize auth with an async database factory function. Returns the cached instance on subsequent calls.

typescript
import { getAuth } from '@mantleframework/auth'

const auth = await getAuth(
  () => getDrizzleClient({ provider: 'aurora-dsql', endpoint: '...' }),
  { secret: '...', baseURL: '...' },
)

resetAuth()

Reset the cached auth instance so the next call to createAuth or getAuth creates a fresh one. Intended for test isolation.

Session Validation

validateSession(auth, token)

Validate a bearer token against Better Auth and return the authenticated user and session.

typescript
import { validateSession } from '@mantleframework/auth'

const result = await validateSession(auth, bearerToken)
// result.user.id, result.user.email, result.session.expiresAt

Throws UnauthorizedError when the token is missing, invalid, or expired.

validateSessionFromHeader(auth, authorizationHeader)

Extract a bearer token from the Authorization header and validate it in one call.

typescript
const result = await validateSessionFromHeader(auth, event.headers.Authorization)

Throws UnauthorizedError when the header is missing, malformed, or the token is invalid.

extractBearerToken(header)

Parse a bearer token from an Authorization header value. Returns null if the header is missing or malformed.

typescript
const token = extractBearerToken('Bearer abc123')
// 'abc123'

expireSession(auth, token, db)

Expire a session by setting its expiresAt to now, preserving the row for audit and cleanup. Unlike Better Auth's revokeSession() which deletes the session row, this keeps it visible for scheduled cleanup.

typescript
import { expireSession } from '@mantleframework/auth'

await expireSession(auth, bearerToken, db)

Throws UnauthorizedError when the token is invalid or already expired.

getSessionExpirationISO(session)

Return the session expiration as an ISO 8601 string.

typescript
const iso = getSessionExpirationISO(sessionResult)
// '2026-04-28T12:00:00.000Z'

refreshSession(auth, token) :badge[deprecated]

Validate and refresh a session. Deprecated -- use validateSession instead. BetterAuth's getSession() auto-extends sessions when updateAge has elapsed, making a separate refresh function unnecessary.

Session Auth Configuration

One-time setup for defineApiHandler with auth: 'session'.

configureSessionAuth(getDb, config)

Register a session auth factory for use by defineApiHandler with auth: 'session'. Call once at module scope in your Lambda entry file, before any handler that uses session auth.

typescript
import { configureSessionAuth } from '@mantleframework/auth'

configureSessionAuth(
  () => getDrizzleClient({ provider: 'aurora-dsql', endpoint: '...' }),
  { secret: getRequiredEnv('AUTH_SECRET'), baseURL: getRequiredEnv('AUTH_BASE_URL') },
)

getConfiguredSessionAuth()

Return the configured Better Auth instance, resolving it via the registered factory. Throws if configureSessionAuth has not been called.

resetSessionAuth()

Reset the session auth factory. Intended for test isolation.

Permissions

AUTH_TABLE_PERMISSIONS

Table permissions required by Better Auth for session management. Consumed by mantle generate permissions to auto-generate DSQL role grants for Lambdas that import @mantleframework/auth.

typescript
const AUTH_TABLE_PERMISSIONS: TablePermission[] = [
  { table: 'users', operations: [Select, Insert, Update] },
  { table: 'sessions', operations: [Select, Insert, Update, Delete] },
  { table: 'accounts', operations: [Select, Insert, Delete] },
  { table: 'verification', operations: [Select, Insert, Delete] },
]

AUTH_MIGRATION_SQL

SQL string to create all Better Auth tables and indexes. Idempotent (uses IF NOT EXISTS). Creates: users, sessions, accounts, verification tables with appropriate indexes.

Schema

Reusable Drizzle ORM column definitions and table schemas for Better Auth. Instances can spread these into their own pgTable() calls to extend auth tables with additional columns.

Column Definitions

typescript
import {
  AUTH_USER_COLUMNS,
  AUTH_SESSION_COLUMNS,
  AUTH_ACCOUNT_COLUMNS,
  AUTH_VERIFICATION_COLUMNS,
} from '@mantleframework/auth'

Each is a Record<string, AnyPgColumn> that can be spread into pgTable() calls:

typescript
import { pgTable, text } from 'drizzle-orm/pg-core'
import { AUTH_USER_COLUMNS } from '@mantleframework/auth'

export const users = pgTable('users', {
  ...AUTH_USER_COLUMNS,
  customField: text('custom_field'),
})

Default Tables

Pre-built Drizzle tables using the standard column definitions:

typescript
import { authUsers, authSessions, authAccounts, authVerification } from '@mantleframework/auth'

AUTH_SCHEMA_MAPPING

Schema mapping in the format expected by Better Auth's drizzleAdapter. Maps Better Auth model names to Drizzle tables:

typescript
const AUTH_SCHEMA_MAPPING = {
  user: authUsers,
  session: authSessions,
  account: authAccounts,
  verification: authVerification,
}

Pass to drizzleAdapter({ schema: AUTH_SCHEMA_MAPPING }) or let createAuth() apply it automatically.

Types

AuthConfig

typescript
interface AuthConfig {
  secret: string
  baseURL: string
  trustedOrigins?: string[]
  sessionExpiresIn?: number      // seconds, default: 30 days
  sessionUpdateAge?: number      // seconds, default: 24 hours
  socialProviders?: {
    apple?: SocialProviderConfig
    google?: SocialProviderConfig
    github?: SocialProviderConfig
    custom?: CustomOAuthProvider[]
  }
  schema?: Record<string, unknown>
  useSecureCookies?: boolean     // default: true
  databaseGenerateId?: boolean
  encryptOAuthTokens?: boolean   // default: false
  experimentalJoins?: boolean    // default: false (BetterAuth v1.5+)
}

SocialProviderConfig

typescript
interface SocialProviderConfig {
  clientId: string
  clientSecret: string
  enabled?: boolean
  appBundleIdentifier?: string
}

CustomOAuthProvider

typescript
interface CustomOAuthProvider {
  providerId: string
  clientId: string
  clientSecret: string
  enabled?: boolean
  options?: Record<string, unknown>
}

SessionResult

typescript
interface SessionResult {
  user: UserDetails
  session: { id: string; token: string; expiresAt: Date }
}

UserDetails

typescript
interface UserDetails {
  id: string
  email: string
  name?: string
  image?: string
  emailVerified: boolean
}

BetterAuthInstance

Opaque return type of betterAuth() used throughout the auth package.