Skip to content

Advanced Use Cases

Beyond standard OAuth flows, gau exposes APIs for building custom auth flows.

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 cookie
response.headers.set('Set-Cookie', cookie)
// For native/mobile apps: use the token directly
headers: { 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

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 token
  • threshold - Only refresh if the session is past this fraction of its TTL (0-1).

Result includes:

  • token - The new JWT token
  • cookie - Ready-to-use Set-Cookie header value
  • source - 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.


Let users use your app without requiring OAuth sign-up. Later, they can upgrade their guest account by linking an OAuth provider.

  1. 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 },
    })
    }
  2. 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,
    }
    }
  3. Use the middleware helpers to automatically extend sessions on each visit. See the Middleware guide.

  4. 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.


Build a closed beta where users need a valid invite token to join. When they redeem their invite, automatically sign them in.

  1. Use auth.signJWT to create invite tokens. Add a purpose claim to distinguish them from session tokens, and a jti (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
    }
  2. Prevent tokens from being redeemed multiple times by tracking them:

    db/schema.ts
    // ... existing Users and Accounts tables
    export const UsedInviteTokens = sqliteTable('used_invite_tokens', {
    jti: text().primaryKey(),
    usedAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),
    });
  3. 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 token
    const 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 used
    const 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 exists
    const existing = await auth.getUserByEmail(email)
    if (existing)
    return new Response('Email already registered', { status: 400 })
    // Create user and mark token as used
    const 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 immediately
    const { cookie } = await auth.issueSession(newUser.id, {
    data: { inviteRedeemed: true },
    })
    return new Response(JSON.stringify({ success: true }), {
    headers: { 'Set-Cookie': cookie },
    })
    }
  4. The user is now signed in! Prompt them to complete their profile or link an OAuth provider for easier future logins.


Let admins sign in as other users for debugging or support purposes.

routes/api/admin/impersonate/+server.ts
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 },
})
}

Authenticate devices that can’t perform OAuth flows (IoT devices, kiosks, POS terminals).

  1. 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 expiration
    await redis.setex(`claim:${code}`, 300, deviceId) // 5 min expiry
    return code
    }
  2. 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 device
    const { token } = await auth.issueSession(session.user.id, {
    data: { deviceId, isDevice: true },
    ttl: 60 * 60 * 24 * 365, // 1 year
    })
    // Delete the claim code
    await redis.del(`claim:${code}`)
    // Send token to device via your preferred method
    await notifyDevice(deviceId, { token })
    return new Response(JSON.stringify({ success: true }))
    }
  3. The device stores the token and uses it for all API requests:

    fetch('https://api.example.com/data', {
    headers: { Authorization: `Bearer ${storedToken}` }
    })

You can configure JWT behavior globally in createAuth:

auth.ts
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.