Advanced Use Cases
Beyond standard OAuth flows, gau exposes APIs for building custom auth flows.
The issueSession method
Section titled “The issueSession method”This method lets you programmatically create sessions for any user. It returns both a JWT token and a ready-to-use Set-Cookie header.
const { token, cookie, cookieName, maxAge } = await auth.issueSession(userId, { data: { customClaim: 'value' }, // Optional: custom JWT claims ttl: 3600 // Optional: override default TTL (seconds)})
// For web apps: set the cookieresponse.headers.set('Set-Cookie', cookie)
// For native/mobile apps: use the token directlyheaders: { Authorization: `Bearer ${token}` }This enables use cases that OAuth alone can’t handle:
- Guest sessions - Let users use your app without signing up, and still save their data in the db
- Invite redemption - Auto sign-in after claiming an invite
- Admin impersonation - Debug issues as a specific user
- Device claiming - Authenticate IoT devices or kiosks
- Magic links / OTP - Build your own email auth flow
The refreshSession method
Section titled “The refreshSession method”Extend an existing session’s lifetime by issuing a new token. Preserves all custom claims from the original.
You can pass either a raw token string or a Request object, which auto-detects cookie vs bearer token.
const refreshed = await auth.refreshSession(request, { ttl: 3600, // Optional: override TTL threshold: 0.5 // Optional: only refresh if past 50% of TTL})Or a raw token string, and use the source field.
const refreshed = await auth.refreshSession(existingToken, { threshold: 0.5 })
if (refreshed) { if (refreshed.source === 'cookie') { response.headers.set('Set-Cookie', refreshed.cookie) }}Options:
ttl- Override the default TTL for the new tokenthreshold- Only refresh if the session is past this fraction of its TTL (0-1).
Result includes:
token- The new JWT tokencookie- Ready-to-useSet-Cookieheader valuesource- How the original token was provided:'cookie','bearer', or'token'
Returns null if the token is invalid, expired, below threshold, or the user no longer exists.
For framework-specific middleware helpers (SolidStart/SvelteKit), see the Middleware guide.
Guest Login
Section titled “Guest Login”Let users use your app without requiring OAuth sign-up. Later, they can upgrade their guest account by linking an OAuth provider.
-
Create a Guest User
Section titled “Create a Guest User”When a user wants to use your app as a guest, create a user record and issue a session.
Consider not doing this on page visit, instead require a button click or captcha.
routes/api/guest/+server.ts import { auth } from '$lib/server/auth'export async function POST() {const guestUser = await auth.createUser({name: `Guest ${Date.now()}`,})const { cookie } = await auth.issueSession(guestUser.id, {data: { isGuest: true },ttl: 60 * 60 * 24 * 365, // 1 year})return new Response(JSON.stringify({ success: true }), {headers: { 'Set-Cookie': cookie },})} -
Detect Guest Users
Section titled “Detect Guest Users”In your app, check for the guest claim:
routes/+layout.server.ts export async function load({ locals }) {const session = await locals.getSession()return {user: session?.user,isGuest: session?.session?.isGuest === true,}} -
Keep Sessions Alive (Optional)
Section titled “Keep Sessions Alive (Optional)”Use the middleware helpers to automatically extend sessions on each visit. See the Middleware guide.
-
Upgrade Guest to Full Account
Section titled “Upgrade Guest to Full Account”When a guest decides to sign up, they simply sign in with an OAuth provider. Thanks to Account Linking, their existing user record will be linked to the new provider.
Private Beta Invites (with Auto Sign-In)
Section titled “Private Beta Invites (with Auto Sign-In)”Build a closed beta where users need a valid invite token to join. When they redeem their invite, automatically sign them in.
-
Generate Invite Tokens
Section titled “Generate Invite Tokens”Use
auth.signJWTto create invite tokens. Add apurposeclaim to distinguish them from session tokens, and ajti(JWT ID) for tracking:invites.ts import { auth } from './auth'export async function createInviteToken() {const token = await auth.signJWT({purpose: 'beta-invite',jti: crypto.randomUUID(),},{ ttl: 60 * 60 * 24 * 7 }, // Expires in 7 days)return token} -
Store Used Tokens
Section titled “Store Used Tokens”Prevent tokens from being redeemed multiple times by tracking them:
db/schema.ts // ... existing Users and Accounts tablesexport const UsedInviteTokens = sqliteTable('used_invite_tokens', {jti: text().primaryKey(),usedAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),});db/schema.ts // ... existing Users and Accounts tablesexport const UsedInviteTokens = pgTable('used_invite_tokens', {jti: text().primaryKey(),usedAt: timestamp().defaultNow(),}); -
Verify and Redeem Invite
Section titled “Verify and Redeem Invite”When a user redeems their invite, verify the token, create their account, and immediately issue a session:
routes/api/redeem-invite/+server.ts import { auth } from '$lib/server/auth'import { db } from '$lib/server/db'import { UsedInviteTokens } from '$lib/server/db/schema'export async function POST({ request }) {const { token, email } = await request.json()// Verify the invite tokenconst payload = await auth.verifyJWT<{ purpose?: string, jti?: string }>(token)if (payload?.purpose !== 'beta-invite' || !payload.jti)return new Response('Invalid invite token', { status: 400 })// Check if token was already usedconst used = await db.query.UsedInviteTokens.findFirst({where: (t, { eq }) => eq(t.jti, payload.jti),})if (used)return new Response('Invite already used', { status: 400 })// Check if email already existsconst existing = await auth.getUserByEmail(email)if (existing)return new Response('Email already registered', { status: 400 })// Create user and mark token as usedconst newUser = await db.transaction(async (tx) => {await tx.insert(UsedInviteTokens).values({ jti: payload.jti! })return await auth.createUser({ email })})// Auto sign-in: issue a session immediatelyconst { cookie } = await auth.issueSession(newUser.id, {data: { inviteRedeemed: true },})return new Response(JSON.stringify({ success: true }), {headers: { 'Set-Cookie': cookie },})} -
Complete the Profile
Section titled “Complete the Profile”The user is now signed in! Prompt them to complete their profile or link an OAuth provider for easier future logins.
Admin Impersonation
Section titled “Admin Impersonation”Let admins sign in as other users for debugging or support purposes.
import { auth } from '$lib/server/auth'
export async function POST({ request, locals }) { const session = await locals.getSession()
// Verify the requester is an admin if (!session?.user?.isAdmin) return new Response('Forbidden', { status: 403 })
const { targetUserId } = await request.json()
// Issue a short-lived session for the target user const { cookie } = await auth.issueSession(targetUserId, { data: { impersonatedBy: session.user.id, impersonatedAt: Date.now(), }, ttl: 60 * 30, // 30 minute impersonation session })
return new Response(JSON.stringify({ success: true }), { headers: { 'Set-Cookie': cookie }, })}Device / Kiosk Authentication
Section titled “Device / Kiosk Authentication”Authenticate devices that can’t perform OAuth flows (IoT devices, kiosks, POS terminals).
-
Generate a Claim Code
Section titled “Generate a Claim Code”Create a short-lived, human-readable code for the device:
devices.ts export async function generateClaimCode(deviceId: string) {const code = Math.random().toString(36).substring(2, 8).toUpperCase()// Store code -> deviceId mapping with expirationawait redis.setex(`claim:${code}`, 300, deviceId) // 5 min expiryreturn code} -
User Claims the Device
Section titled “User Claims the Device”The user enters the code on your web app (where they’re already authenticated):
routes/api/claim-device/+server.ts export async function POST({ request, locals }) {const session = await locals.getSession()if (!session?.user)return new Response('Unauthorized', { status: 401 })const { code } = await request.json()const deviceId = await redis.get(`claim:${code}`)if (!deviceId)return new Response('Invalid or expired code', { status: 400 })// Issue a long-lived session for the deviceconst { token } = await auth.issueSession(session.user.id, {data: { deviceId, isDevice: true },ttl: 60 * 60 * 24 * 365, // 1 year})// Delete the claim codeawait redis.del(`claim:${code}`)// Send token to device via your preferred methodawait notifyDevice(deviceId, { token })return new Response(JSON.stringify({ success: true }))} -
Device Uses Token
Section titled “Device Uses Token”The device stores the token and uses it for all API requests:
fetch('https://api.example.com/data', {headers: { Authorization: `Bearer ${storedToken}` }})
JWT Configuration
Section titled “JWT Configuration”You can configure JWT behavior globally in createAuth:
export const auth = createAuth({ // ... jwt: { secret: process.env.AUTH_SECRET, algorithm: 'ES256', // or 'HS256' ttl: 3600 * 24 * 7, // Default: 7 days iss: 'my-app', // Issuer claim aud: 'my-audience', // Audience claim },})The issueSession method respects these defaults but allows per-call overrides via the ttl option.
See the jwt option in the configuration guide for more details.