SvelteKit Integration
Fire Shield provides SvelteKit hooks and server-side middleware for full-stack RBAC authorization.
Features
- Server-side hooks for request protection
- Client-side permission checks
- TypeScript support with
App.Localsaugmentation - SSR-compatible
- Works with all SvelteKit adapters
- Seamless integration with SvelteKit's load functions
Installation
bash
npm install @fire-shield/sveltekit @fire-shield/coreQuick Start
1. Initialize RBAC
typescript
// src/lib/rbac.ts
import { RBAC } from '@fire-shield/core';
export const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);2. Setup Hooks
typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { rbac } from '$lib/rbac';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
// Get user from session/cookie
const session = await event.locals.getSession();
return session?.user;
}
});3. Use in Pages
svelte
<!-- src/routes/admin/+page.svelte -->
<script lang="ts">
import { page } from '$app/stores';
const { user, rbac } = $page.data;
const canManageUsers = rbac.hasPermission(user, 'user:write');
</script>
{#if canManageUsers}
<button>Create User</button>
{/if}API
createRBACHandle(rbac, options)
Creates a SvelteKit hook for RBAC authorization.
Parameters:
typescript
interface RBACHandleOptions {
getUser?: (event: RequestEvent) => Promise<RBACUser | undefined> | RBACUser | undefined;
onUnauthorized?: (event: RequestEvent, result: AuthorizationResult) => Response;
}Example:
typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
const sessionId = event.cookies.get('session');
if (!sessionId) return undefined;
return await getUserFromSession(sessionId);
},
onUnauthorized: (event, result) => {
throw redirect(303, '/login');
}
});requirePermission(permission)
Server-side guard for protecting routes.
Example:
typescript
// src/routes/admin/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
export const load = requirePermission('admin:access', async (event) => {
// This only runs if user has 'admin:access' permission
const users = await getUsers();
return { users };
});requireRole(role)
Server-side guard for role-based protection.
Example:
typescript
// src/routes/admin/+page.server.ts
import { requireRole } from '@fire-shield/sveltekit';
export const load = requireRole('admin', async (event) => {
// This only runs if user has 'admin' role
const settings = await getAdminSettings();
return { settings };
});Server-Side Protection
Protecting Load Functions
typescript
// src/routes/posts/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
export const load = requirePermission('post:read', async ({ locals }) => {
const posts = await db.post.findMany();
return { posts };
});Protecting Actions
typescript
// src/routes/posts/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
import { fail } from '@sveltejs/kit';
export const actions = {
create: requirePermission('post:write', async ({ request, locals }) => {
const data = await request.formData();
const title = data.get('title');
await db.post.create({
data: { title, authorId: locals.user.id }
});
return { success: true };
}),
delete: requirePermission('post:delete', async ({ request }) => {
const data = await request.formData();
const postId = data.get('postId');
await db.post.delete({ where: { id: postId } });
return { success: true };
})
};Custom Authorization Logic
typescript
// src/routes/posts/[id]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, locals }) => {
const post = await db.post.findUnique({ where: { id: params.id } });
// Custom authorization: Check if user owns the post or is admin
const canEdit =
locals.rbac.hasPermission(locals.user, 'post:edit') &&
(post.authorId === locals.user.id || locals.rbac.hasRole(locals.user, 'admin'));
return {
post,
canEdit
};
};Client-Side Usage
In Svelte Components
svelte
<!-- src/routes/posts/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
const { user, rbac } = data;
</script>
<div>
{#if rbac.hasPermission(user, 'post:write')}
<button>Create Post</button>
{/if}
{#if rbac.hasPermission(user, 'post:delete')}
<button>Delete All Posts</button>
{/if}
</div>Reactive Permission Checks
svelte
<script lang="ts">
import { page } from '$app/stores';
$: user = $page.data.user;
$: rbac = $page.data.rbac;
$: canWrite = rbac.hasPermission(user, 'post:write');
$: isAdmin = rbac.hasRole(user, 'admin');
</script>
{#if canWrite}
<CreatePostForm />
{/if}
{#if isAdmin}
<AdminPanel />
{/if}TypeScript Setup
Augment App.Locals for type safety:
typescript
// src/app.d.ts
import type { RBAC, RBACUser } from '@fire-shield/core';
declare global {
namespace App {
interface Locals {
user?: RBACUser;
rbac: RBAC;
}
}
}
export {};Layout Load Functions
Share RBAC data across all pages:
typescript
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.user,
rbac: locals.rbac
};
};Now all pages have access to user and rbac:
svelte
<!-- Any page -->
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
const { user, rbac } = data;
</script>Form Actions with Authorization
svelte
<!-- src/routes/posts/create/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageData } from './$types';
export let data: PageData;
</script>
{#if data.rbac.hasPermission(data.user, 'post:write')}
<form method="POST" use:enhance>
<input type="text" name="title" required />
<textarea name="content" required></textarea>
<button type="submit">Create Post</button>
</form>
{:else}
<p>You don't have permission to create posts.</p>
{/if}typescript
// src/routes/posts/create/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
export const actions = {
default: requirePermission('post:write', async ({ request, locals }) => {
const data = await request.formData();
await db.post.create({
data: {
title: data.get('title'),
content: data.get('content'),
authorId: locals.user.id
}
});
throw redirect(303, '/posts');
})
};API Routes Protection
typescript
// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import { requirePermission } from '@fire-shield/sveltekit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = requirePermission('user:read', async () => {
const users = await db.user.findMany();
return json(users);
});
export const POST: RequestHandler = requirePermission('user:write', async ({ request }) => {
const data = await request.json();
const user = await db.user.create({ data });
return json(user, { status: 201 });
});
export const DELETE: RequestHandler = requirePermission('user:delete', async ({ request }) => {
const { id } = await request.json();
await db.user.delete({ where: { id } });
return json({ success: true });
});Error Handling
typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { error } from '@sveltejs/kit';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
return await getUserFromSession(event);
},
onUnauthorized: (event, result) => {
throw error(403, {
message: 'Access Denied',
reason: result.reason
});
}
});Best Practices
- Use server-side protection - Always protect sensitive operations on the server
- Share RBAC in layouts - Make user and rbac available to all pages via layout load
- Type-safe with App.Locals - Augment App.Locals for TypeScript support
- Cache permission checks - Enable v2.2.0 caching for better performance
- Handle unauthorized gracefully - Provide good error messages and redirects
Advanced Example
Complete authentication flow with protected routes:
typescript
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { createRBACHandle } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
const authHandle = async ({ event, resolve }) => {
const sessionId = event.cookies.get('session');
if (sessionId) {
const user = await getUserFromSession(sessionId);
event.locals.user = user;
}
return resolve(event);
};
const rbacHandle = createRBACHandle(rbac, {
getUser: (event) => event.locals.user,
onUnauthorized: () => {
throw redirect(303, '/login');
}
});
export const handle = sequence(authHandle, rbacHandle);Next Steps
- Svelte Integration - Client-side Svelte features
- Core Concepts - Understanding permissions
- API Reference - Complete API documentation
