Authentication

The auth callback is what lets the test runner log in as the user your up request created. It receives that user and returns credentials the runner authenticates with.

What the callback receives

auth: async (user, context) => {
// user: the first User record from refs, or null if the scenario has no User.
// Always handle null. Shape: { id, name, email, ... }
// context:
// scopeValue - the detected scope value (e.g. organization id), or the testRunId fallback
// refs - all created records, keyed by model, for looking up related data
}

Not every scenario creates a User, so user can be null. Guard for it.

What the callback returns

interface AuthResult {
cookies?: Array<{
name: string
value: string
httpOnly?: boolean
sameSite?: 'strict' | 'lax' | 'none'
path?: string
domain?: string
secure?: boolean
maxAge?: number
}>
headers?: Record<string, string> // custom headers, e.g. Authorization: Bearer ...
credentials?: Record<string, string> // key/value pairs for a manual login flow
}

There is no top-level token field. Return a bearer token on headers; return login credentials on credentials.

Pattern 1 - Session cookies

The default for most server-rendered web apps. Create a real session and return its cookie.

auth: async (user) => {
const session = await lucia.createSession(user!.id, {})
const cookie = lucia.createSessionCookie(session.id)
return {
cookies: [{
name: cookie.name,
value: cookie.value,
httpOnly: true,
sameSite: 'lax',
path: '/',
}],
}
}

Pattern 2 - Bearer token

For APIs and SPAs that authenticate with an Authorization header.

auth: async (user) => {
const token = jwt.sign(
{ sub: user!.id, email: user!.email },
process.env.JWT_SECRET!,
{ expiresIn: '1h' },
)
return { headers: { Authorization: `Bearer ${token}` } }
}

Pattern 3 - Email/password credentials

When the agent needs to log in through your app’s actual login screen instead of receiving a cookie or token, return credentials.

auth: async (user) => ({
credentials: {
email: user!.email,
password: 'test-password-123',
},
})

For this to work, the User must be created with a known password. Hash it in the User factory during create:

User: defineFactory({
inputSchema: z.object({ email: z.string(), name: z.string() }),
create: async (data) =>
userService.create({ ...data, password: 'test-password-123' }),
})

Common mistakes

MistakeWhat happensFix
Returning a hardcoded "test-token"Every test fails at loginUse your real session / JWT creation
No password set on the UserEmail/password login failsHash a known password in the User factory
Token expires too quicklyTests fail midway throughSet expiry to at least 1 hour
Wrong cookie nameThe browser never sends the cookieCheck your app’s real cookie name in DevTools
Link copied