Auth
Connection auth - upgrade()
The upgrade function in src/hooks.ws.js runs on every new WebSocket connection. Return user data to attach to the connection, or false to reject it.
// src/hooks.ws.js
export function upgrade({ cookies }) {
const session = validateSession(cookies.session_id);
if (!session) return false;
return { id: session.userId, name: session.name };
} Whatever you return is available as ctx.user in all live() functions.
Per-module auth - guard()
Export a _guard from any src/live/ file. It runs before every function in that file.
// src/live/admin.js
import { live, guard, LiveError } from 'svelte-realtime/server';
export const _guard = guard((ctx) => {
if (ctx.user?.role !== 'admin')
throw new LiveError('FORBIDDEN', 'Admin only');
});
export const deleteUser = live(async (ctx, userId) => {
await db.users.delete(userId);
}); Both functions in this file require admin access.
Composable guards
Chain multiple guards. They run in order, and earlier ones can enrich ctx for later ones:
export const _guard = guard(
(ctx) => {
if (!ctx.user) throw new LiveError('UNAUTHORIZED');
},
(ctx) => {
ctx.permissions = lookupPermissions(ctx.user.id);
},
(ctx) => {
if (!ctx.permissions.includes('write'))
throw new LiveError('FORBIDDEN');
}
); Per-function auth
Check ctx.user inside any live() function:
export const deleteItem = live(async (ctx, id) => {
if (!ctx.user) throw new LiveError('UNAUTHORIZED');
if (ctx.user.role !== 'admin') throw new LiveError('FORBIDDEN');
await db.items.delete(id);
}); Stream access control
Use the access option on live.stream() to control who can subscribe. The predicate receives ctx and is checked once at subscription time. If it returns false, the subscription is denied with { ok: false, code: 'FORBIDDEN', error: 'Access denied' } and no data is sent. For per-event filtering, use pipe.filter().
// Only admins can subscribe
export const adminFeed = live.stream('admin-feed', async (ctx) => {
return db.adminEvents.recent();
}, {
merge: 'crud',
access: (ctx) => ctx.user?.role === 'admin'
});
// Role-based: different roles get different access
export const items = live.stream('items', async (ctx) => {
return db.items.all();
}, {
merge: 'crud',
access: live.access.role({
admin: true,
viewer: false
})
}); For per-user data isolation, use dynamic topics so each user subscribes to their own topic:
// Each user gets their own topic - no cross-user data leakage
export const myOrders = live.stream(
(ctx) => `orders:${ctx.user.id}`,
async (ctx) => db.orders.forUser(ctx.user.id),
{ merge: 'crud', key: 'id' }
); | Helper | Description |
|---|---|
live.access.owner(field?) | Subscription allowed if ctx.user[field] is present (default: 'id') |
live.access.team() | Subscription allowed if ctx.user.teamId is present |
live.access.role(map) | Role-based: { admin: true, viewer: (ctx) => ... } |
live.access.any(...predicates) | OR: any predicate returning true allows the subscription |
live.access.all(...predicates) | AND: all predicates must return true |
Global middleware
Use live.middleware() for cross-cutting auth:
import { live, LiveError } from 'svelte-realtime/server';
live.middleware(async (ctx, next) => {
if (!ctx.user) throw new LiveError('UNAUTHORIZED');
return next();
}); Under the hood, upgrade() runs inside the adapter’s WebSocket handshake. See svelte-adapter-uws WebSocket docs for the full connection lifecycle.
Was this page helpful?