Skip to content

Authentication

PocketKit comes with a complete authentication system built on PocketBase. This guide explains how it works and how to customize it.

PocketKit uses cookie-based authentication with server-side session validation. Here’s how the complete flow works:

sequenceDiagram
    participant User
    participant Browser
    participant SvelteKit
    participant Hooks
    participant PocketBase

    User->>Browser: Visit /auth/login
    Browser->>SvelteKit: GET /auth/login
    SvelteKit-->>Browser: Login form

    User->>Browser: Submit credentials
    Browser->>SvelteKit: POST /auth/login
    SvelteKit->>PocketBase: authWithPassword(email, password)
    PocketBase-->>SvelteKit: User data + auth token
    SvelteKit->>Browser: Set cookie + redirect
    Browser->>SvelteKit: GET / (with auth cookie)

    SvelteKit->>Hooks: handle() intercepts request
    Hooks->>PocketBase: Validate auth cookie
    PocketBase-->>Hooks: User data (if valid)
    Hooks->>SvelteKit: Set locals.pbUser
    SvelteKit-->>Browser: Page with user context
graph TB
    subgraph Client
        A[Browser]
        B[Svelte Components]
    end

    subgraph "SvelteKit Server"
        C[hooks.server.ts]
        D[+page.server.ts]
        E[+server.ts endpoints]
        F[Server Helpers]
    end

    subgraph "PocketBase Backend"
        G[Auth API]
        H[Users Collection]
        I[SQLite Database]
    end

    A -->|HTTP Request with Cookie| C
    C -->|Validates Auth| F
    F -->|Check Auth State| G
    G -->|Query| H
    H -->|Read/Write| I

    C -->|Set locals.pbUser| D
    D -->|Load Function| B

    B -->|Form Actions| E
    E -->|Auth Requests| G

    style C fill:#ff3e00,stroke:#ff3e00,color:#fff
    style F fill:#ff3e00,stroke:#ff3e00,color:#fff
    style G fill:#2563eb,stroke:#1d4ed8,color:#fff
    style I fill:#2563eb,stroke:#1d4ed8,color:#fff

Location: app/src/lib/pocketbase.ts

export const pb = new PocketBase(
env.PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090'
);
export const currentUser = writable(pb.authStore.record);
pb.authStore.onChange(() => {
currentUser.set(pb.authStore.record);
});

Purpose:

  • Creates a PocketBase client that runs in the browser
  • Manages real-time auth state updates
  • Provides a Svelte store for reactive user data

Location: app/src/lib/server/pocketbase.ts

This file contains several helper functions for server-side auth:

Creates a fresh PocketBase instance for server use.

Creates a PocketBase instance and loads auth from cookies.

Saves the current auth state to an HTTP-only cookie.

Removes the auth cookie (for logout).

Validates the current session and refreshes the token if needed.

Location: app/src/hooks.server.ts

The server hook runs on every request before any page loads:

export const handle: Handle = async ({ event, resolve }) => {
const pb = createServerPbWithAuth(event);
const user = await validatePbAuth(pb);
if (user) {
event.locals.pbUser = {
id: user.id,
email: user.email,
// ... other user fields
};
savePbAuthCookie(event, pb);
} else {
event.locals.pbUser = null;
clearPbAuthCookie(event);
}
return resolve(event);
};

What this does:

  1. Creates a PocketBase instance with auth from cookies
  2. Validates the auth token (and refreshes if needed)
  3. Sets event.locals.pbUser for use in server-side code
  4. Updates or clears the auth cookie
  5. Continues to the requested page

Location: app/src/routes/+layout.server.ts

export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.pbUser
};
};

This makes the user data available to all pages via $page.data.user.

sequenceDiagram
    participant User
    participant RegisterPage
    participant PageServer
    participant PocketBase
    participant Database

    User->>RegisterPage: Fill registration form
    RegisterPage->>PageServer: POST /auth/register
    PageServer->>PocketBase: collection('users').create()
    PocketBase->>Database: INSERT new user
    Database-->>PocketBase: User created
    PocketBase-->>PageServer: User record
    PageServer->>PocketBase: authWithPassword()
    PocketBase-->>PageServer: Auth token
    PageServer->>PageServer: savePbAuthCookie()
    PageServer-->>RegisterPage: Redirect to /

Location: app/src/routes/auth/register/+page.server.ts

Key steps:

  1. Create user record in PocketBase
  2. Automatically log them in
  3. Save auth cookie
  4. Redirect to home page
sequenceDiagram
    participant User
    participant LoginPage
    participant PageServer
    participant PocketBase

    User->>LoginPage: Enter email & password
    LoginPage->>PageServer: POST /auth/login
    PageServer->>PocketBase: authWithPassword(email, password)

    alt Valid credentials
        PocketBase-->>PageServer: Auth token + user data
        PageServer->>PageServer: savePbAuthCookie()
        PageServer-->>LoginPage: Redirect to /
    else Invalid credentials
        PocketBase-->>PageServer: Error
        PageServer-->>LoginPage: Show error message
    end

Location: app/src/routes/auth/login/+page.server.ts

sequenceDiagram
    participant User
    participant App
    participant LogoutEndpoint
    participant Browser

    User->>App: Click logout
    App->>LogoutEndpoint: POST /auth/logout
    LogoutEndpoint->>LogoutEndpoint: clearPbAuthCookie()
    LogoutEndpoint-->>Browser: Clear cookie + redirect to /
    Browser->>App: Reload as logged out

Location: app/src/routes/auth/logout/+server.ts

graph LR
    A[Incoming Request] --> B{Has auth cookie?}
    B -->|No| C[Set user = null]
    B -->|Yes| D[Load auth from cookie]
    D --> E{Token valid?}
    E -->|No| C
    E -->|Yes| F{Token expired?}
    F -->|Yes| G[Refresh token]
    F -->|No| H[Load user data]
    G --> H
    H --> I[Set locals.pbUser]
    C --> J[Continue to page]
    I --> J

    style A fill:#f59e0b,stroke:#d97706,color:#fff
    style I fill:#10b981,stroke:#059669,color:#fff
    style C fill:#ef4444,stroke:#dc2626,color:#fff
    style J fill:#6366f1,stroke:#4f46e5,color:#fff

This happens automatically in hooks.server.ts for every request.

Auth tokens are stored in HTTP-only cookies, which means:

  • JavaScript cannot access them (prevents XSS attacks)
  • Automatically sent with every request
  • Secure flag in production (HTTPS only)

Configuration in savePbAuthCookie():

event.cookies.set(pbCookieName, match[1], {
httpOnly: true, // Not accessible via JavaScript
secure: import.meta.env.PROD, // HTTPS only in production
sameSite: 'lax', // CSRF protection
path: '/', // Available to all routes
maxAge: 60 * 60 * 24 * 7 // 7 days
});

Tokens are automatically refreshed in validatePbAuth():

export async function validatePbAuth(pb: PocketBase) {
if (!pb.authStore.isValid) {
return null;
}
try {
await pb.collection('users').authRefresh();
return pb.authStore.record;
} catch {
pb.authStore.clear();
return null;
}
}

This ensures sessions stay valid without requiring re-login.

All auth checks happen server-side in hooks.server.ts. The client never makes trust decisions about authentication.

Add checks in your +page.server.ts or +server.ts files:

import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
if (!locals.pbUser) {
throw error(401, 'Unauthorized');
}
// Protected content here
return {
// ... data
};
};

In your Svelte components, use the user data from the layout:

<script lang="ts">
import { page } from '$app/stores';
$: user = $page.data.user;
</script>
{#if user}
<p>Welcome, {user.email}!</p>
<a href="/auth/logout">Logout</a>
{:else}
<a href="/auth/login">Login</a>
{/if}

PocketBase supports OAuth2 providers (Google, Facebook, GitHub, etc.).

  1. Go to PocketBase Admin → Settings → Auth providers
  2. Enable and configure your providers
  3. Update your login page to include OAuth buttons
  4. Use pb.collection('users').authWithOAuth2()

Enable in PocketBase Admin:

  1. Settings → Mail settings (configure SMTP)
  2. Collections → users → Options
  3. Enable “Require email verification”

PocketBase provides built-in password reset:

// Request password reset
await pb.collection('users').requestPasswordReset('user@example.com');
// Confirm with token
await pb.collection('users').confirmPasswordReset(
token,
newPassword,
newPasswordConfirm
);

Add fields to the users collection in PocketBase Admin:

  1. Collections → users → Fields
  2. Add your custom fields (e.g., “name”, “avatar”, “role”)
  3. Update the PbUser type in app/src/lib/server/pocketbase.ts

The user type is defined in app/src/lib/server/pocketbase.ts:

export type PbUser = {
id: string;
email: string;
username?: string;
verified: boolean;
created: string;
updated: string;
};

And made available via app types in app/src/app.d.ts:

declare namespace App {
interface Locals {
pbUser: PbUser | null;
}
}

Via PocketBase Admin:

  1. Visit http://localhost:8090/_ (or your production URL + /_)
  2. Go to Collections → users
  3. Click “New record”
  4. Fill in email and password
  5. Save

Via API:

const user = await pb.collection('users').create({
email: 'test@example.com',
password: 'testpassword123',
passwordConfirm: 'testpassword123'
});
  1. Visit /auth/register and create an account
  2. Check that you’re redirected to / and see your email
  3. Click logout
  4. Visit /auth/login and log back in
  5. Verify session persists after page refresh

Cause: Cookie not being saved correctly

Fix:

  • Check PUBLIC_POCKETBASE_URL environment variable
  • Ensure CORS is configured in PocketBase
  • Verify cookies are being set (check DevTools → Application → Cookies)

Cause: Token validation failed

Fix:

  • Clear cookies and log in again
  • Check that PocketBase backend is running
  • Verify the auth refresh endpoint is working

Cause: Email verification might be required

Fix:

  • Check PocketBase settings for email verification requirements
  • Disable email verification for development
  • Configure SMTP for production email verification

Now that you understand authentication: