Skip to main content

Authentication & Authorization

Session management, 2FA, access control, and multi-tenancy.

Package: @repo/auth Built on: Better Auth

Session Management

Sessions flow from locals.sessiondata.session via layout files.

In Svelte components, access via page data:

<script lang="ts">
import { page } from '$app/state'

const session = page.data.session
const user = session?.user
</script>

{#if user}
<p>Welcome, {user.name}</p>
{:else}
<a href="/login">Sign in</a>
{/if}

Or via props when passed from layout:

<script lang="ts">
const { data } = $props()
const user = data.session?.user
</script>

In remote files (server-side):

// *.remote.ts
import { getUserSession } from '@repo/auth/server'

export const getProfile = query(async () => {
const session = getUserSession()
if (!session) throw new Error('Unauthorized')

return db.select().from(users).where(eq(users.id, session.user.id))
})

Two-Factor Authentication (2FA)

TOTP-based 2FA is included in the boilerplate. See the security settings page for the implementation.

Gate-Based Access Control

Gates are functions that take a session and return a boolean. Use them for authorization checks.

Permission Gates

Use createPermissionGate for role/permission-based checks:

// domains/users/utils/gates.ts
import { createPermissionGate } from '$lib/library/authHelpers'

export function userDeleteGates(id: string) {
return [
createPermissionGate('users', ['delete']),
]
}

Custom Gates

Create custom gates for complex logic:

// domains/organizations/utils/gates.ts
import type { Gate, Session } from '@repo/auth'
import { createPermissionGate } from '$lib/library/authHelpers'

export function userInOrganizationGate(organizationId: string): Gate {
return (session: Session | null) =>
session?.user?.organizations?.some(org => org.id === organizationId) || false
}

export function organizationDeleteGates(id: string) {
return [
createPermissionGate('organizations', ['delete']),
userInOrganizationGate(id),
]
}

Check Permissions in Components

<script lang="ts">
import { page } from '$app/state'
import { hasAccess } from '@repo/auth'
import { organizationDeleteGates } from '$lib/domains/organizations/utils/gates'

const { params } = $props()
const canDelete = await hasAccess(page.data.session, organizationDeleteGates(params.id))
</script>

{#if canDelete}
<button class="text-red-500">Delete</button>
{/if}

Server-Side Authorization

Use validateSession with gates in remote files:

// organizations.remote.ts
import { form } from '$app/server'
import { validateSession } from '$lib/library/authHelpers.server'
import { organizationDeleteGates } from '$lib/domains/organizations/utils/gates'

export const deleteOrganization = form(schema, async ({ id }) => {
await validateSession(organizationDeleteGates(id))

// User is authorized, proceed with deletion
return db.delete(organizations).where(eq(organizations.id, id))
})

Multi-Tenant Support

MonoStack supports multi-tenant applications with separate schemas per tenant.

Tenant-Scoped Queries

Use getTenantDb from your app's managed database to get a db connection scoped to the current organization:

// *.remote.ts
import { getTenantDb } from '$lib/db/managedDb.server'

export const getProducts = query(async () => {
const { db } = await getTenantDb()
return db.select().from(products)
})

Organization Switching

See the boilerplate's +layout.svelte for organization switching implementation.

Invitations

Invitations are managed via the /invitations route. See the boilerplate for the full implementation.

Permissions

Use PermissionsField for editing role permissions:

// domains/roles/utils/schemas.ts
import { PermissionsField } from '@repo/auth'
import { authDomains } from '$lib/library/domainActions'
import { zaf } from '@repo/form'

export const roleInsertSchema = createInsertSchema(rolesTable)
.pick({ name: true })
.extend({
permissions: zaf(z.array(permissionInsertSchema).default([]), {
component: PermissionsField,
props: {
domains: authDomains,
},
}),
})

Full Documentation

See @repo/auth README for complete API reference.