LogoRobo.js

Session management

Session strategies, server-side validation, token extraction, and multi-account session switching.

The plugin supports JWT and database session strategies. When a storage adapter is present, it defaults to database sessions. Both strategies support server-side validation via getServerSession and getToken.

Session strategies

The plugin provides two session strategies that determine how session data is stored and validated.

JWT (default without adapter):

  • Stateless — session data stored in a signed cookie
  • No server-side storage needed
  • Session expires based on session.maxAge (default 30 days)

Database (default with adapter):

  • Server-side session records
  • Sessions can be revoked individually
  • Required for multi-account session stack
  • Expired sessions pruned on read
config/plugins/robojs/auth.ts
export default <AuthPluginOptions>{
  session: {
    strategy: 'database',  // or 'jwt'
    maxAge: 60 * 60 * 24 * 30,  // 30 days
    updateAge: 60 * 60 * 24     // refresh every 24 hours
  }
}
config/plugins/robojs/auth.js
export default {
  session: {
    strategy: 'database',  // or 'jwt'
    maxAge: 60 * 60 * 24 * 30,  // 30 days
    updateAge: 60 * 60 * 24     // refresh every 24 hours
  }
}

Strategy auto-detects: 'database' when an adapter is present, 'jwt' otherwise. Override explicitly if needed.

Server-side session access

Use getServerSession to validate sessions in API routes or server-side handlers. Accepts Request, Headers, or a plain header record.

import { getServerSession } from '@robojs/auth'

// From a Robo API route
export default async (request: RoboRequest) => {
  const session = await getServerSession(request)
  if (!session) return new Response('Unauthorized', { status: 401 })
  return { user: session.user }
}
import { getServerSession } from '@robojs/auth'

// From a Robo API route
export default async (request) => {
  const session = await getServerSession(request)
  if (!session) return new Response('Unauthorized', { status: 401 })
  return { user: session.user }
}

Returns Session | null. The session object includes user, expires, and emailVerified status.

Extract tokens:

import { getToken } from '@robojs/auth'

// Get decoded JWT payload
const token = await getToken(request)

// Get raw cookie value
const raw = await getToken(request, { raw: true })
import { getToken } from '@robojs/auth'

// Get decoded JWT payload
const token = await getToken(request)

// Get raw cookie value
const raw = await getToken(request, { raw: true })

For JWT strategy, decodes the token locally. For database strategy, delegates to getServerSession or returns the raw cookie value with { raw: true }.

Session callbacks

The plugin composes a wrapper around Auth.js session callback to inject emailVerified status automatically. User-provided callbacks run first, then the plugin's enhancement executes.

config/plugins/robojs/auth.ts
export default <AuthPluginOptions>{
  callbacks: {
async session({ session, token, user }) {
  // emailVerified is automatically available
  console.log(session.emailVerified)

  // Add custom data
  if (token?.sub) session.user.id = token.sub
  return session
}
  }
}
config/plugins/robojs/auth.js
export default {
  callbacks: {
async session({ session, token, user }) {
  // emailVerified is automatically available
  console.log(session.emailVerified)

  // Add custom data
  if (token?.sub) session.user.id = token.sub
  return session
}
  }
}

The composed callback ensures emailVerified is always present in the session object, sourced from the adapter's user record.

Multi-account session stack

Multi-account session switching requires database session strategy.

The plugin supports up to 5 simultaneous sessions per device. Users can sign into multiple accounts and switch between them without re-authenticating.

How it works:

  • Each sign-in adds a session to a device-specific stack (stored in a cookie)
  • The active session determines the user's identity
  • Switching activates a different session from the stack
  • Maximum 5 sessions per device (MAX_STACK_SIZE)

Server routes (registered automatically with database strategy):

MethodPathDescription
GET/api/auth/sessionsList all device sessions
POST/api/auth/sessions/switchSwitch active session (CSRF-protected)
POST/api/auth/sessions/removeRemove a session from the stack
POST/api/auth/sessions/clearRemove all sessions and sign out

Client helpers:

import { getSessions, switchSession, removeSession, clearSessions } from '@robojs/auth/client'

// List all sessions on this device
const sessions = await getSessions()
// Returns: DeviceSession[]
import { getSessions, switchSession, removeSession, clearSessions } from '@robojs/auth/client'

// List all sessions on this device
const sessions = await getSessions()
// Returns: DeviceSession[]

DeviceSession type:

FieldTypeDescription
userIdstringUser ID
namestring | nullDisplay name
emailstring | nullEmail address
imagestring | nullAvatar URL
isActivebooleanWhether this is the current session
isExpiredbooleanWhether the session has expired
// Switch to a different account
const result = await switchSession('user-id-123')
// Returns: SwitchSessionResult

if (result.ok) {
  console.log('Switched to:', result.session?.user)
} else {
  console.log('Failed:', result.error)
  // error: 'session_expired' | 'not_found' | 'forbidden'
}
// Switch to a different account
const result = await switchSession('user-id-123')
// Returns: SwitchSessionResult

if (result.ok) {
  console.log('Switched to:', result.session?.user)
} else {
  console.log('Failed:', result.error)
  // error: 'session_expired' | 'not_found' | 'forbidden'
}
// Remove a specific session
await removeSession('user-id-456')
// If the active session is removed, auto-switches to the most recent valid session

// Sign out of all accounts
await clearSessions()
// Remove a specific session
await removeSession('user-id-456')
// If the active session is removed, auto-switches to the most recent valid session

// Sign out of all accounts
await clearSessions()

Self-healing behavior:

  • If the active session is missing from the stack, it is automatically re-added
  • When a session is removed, the system auto-switches to the most recent valid session
  • Expired sessions are detected and flagged in the response

Credentials and database sessions

Auth.js 0.40 normally prevents database sessions with credentials-only providers. The plugin includes a compatibility patch that bypasses this restriction, so database sessions work with EmailPassword out of the box.

The plugin patches Array.prototype.some to disable Auth.js's assertion against credentials providers with database sessions. This patch is cached on the config object to avoid repeated application.

Next steps

On this page