npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sp-uvb/sveltekit

v0.1.0

Published

SvelteKit hooks and utilities for Universal Verification Broker (UVB) authentication

Readme

@sp-uvb/sveltekit

Production-grade SvelteKit integration for Universal Verification Broker (UVB) authentication. Provides server hooks, load function utilities, API route helpers, and Svelte stores.

Installation

npm install @sp-uvb/sveltekit
# or
yarn add @sp-uvb/sveltekit
# or
pnpm add @sp-uvb/sveltekit

Quick Start

1. Configure Server Hook

Create or update src/hooks.server.ts:

import { createUvbHandle } from '@sp-uvb/sveltekit/hooks';
import { sequence } from '@sveltejs/kit/hooks';

export const handle = createUvbHandle({
  tenantId: 'your-tenant-id',
  uvbUrl: 'http://localhost:8080',
  cookieName: 'uvb_session',
  excludePaths: ['/login', '/register', '/api/public'],
});

// If you have other hooks, sequence them:
export const handle = sequence(createUvbHandle({ tenantId: 'your-tenant-id' }), yourOtherHook);

2. Use in Server Load Functions

// src/routes/profile/+page.server.ts
import { requireUvbSession } from '@sp-uvb/sveltekit/server';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  const session = requireUvbSession(event);

  return {
    user: {
      id: session.userId,
      factors: session.factorsVerified,
    },
  };
};

3. Use in API Routes

// src/routes/api/profile/+server.ts
import { requireUvbSession, requireFactors } from '@sp-uvb/sveltekit/server';
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async (event) => {
  const session = requireUvbSession(event);

  return json({
    userId: session.userId,
    tenantId: session.tenantId,
  });
};

export const DELETE: RequestHandler = async (event) => {
  // Require strong MFA for destructive operations
  const session = requireFactors(event, ['totp', 'webauthn']);

  // Perform deletion
  return json({ success: true });
};

4. Use in Svelte Components

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { onMount } from 'svelte'
  import { uvbSession, isAuthenticated } from '@sp-uvb/sveltekit/client'
  import type { PageData } from './$types'

  export let data: PageData

  // Initialize session from server
  onMount(() => {
    if (data.uvbSession) {
      $uvbSession = data.uvbSession
    }
  })
</script>

{#if $isAuthenticated}
  <nav>
    <a href="/profile">Profile</a>
    <span>User: {$uvbSession?.userId}</span>
  </nav>
  <slot />
{:else}
  <a href="/login">Login</a>
{/if}

API Reference

Hooks (@sp-uvb/sveltekit/hooks)

createUvbHandle(options)

Creates a SvelteKit handle hook for authentication.

Options:

interface UvbHandleOptions {
  uvbUrl?: string; // Default: 'http://localhost:8080'
  tenantId: string; // Required: Your UVB tenant ID
  apiKey?: string; // Optional: API key for server-to-server auth
  cookieName?: string; // Default: 'uvb_session'
  excludePaths?: string[]; // Default: []
}

Example:

import { createUvbHandle } from '@sp-uvb/sveltekit/hooks';

export const handle = createUvbHandle({
  tenantId: import.meta.env.VITE_UVB_TENANT_ID,
  uvbUrl: import.meta.env.VITE_UVB_URL,
  apiKey: import.meta.env.UVB_API_KEY,
  excludePaths: ['/login', '/register', '/api/public'],
});

sequence(...handlers)

Utility to combine multiple handle hooks.

import { createUvbHandle, sequence } from '@sp-uvb/sveltekit/hooks';

const uvbHandle = createUvbHandle({ tenantId: 'my-tenant' });
const loggingHandle = async ({ event, resolve }) => {
  console.log(`Request: ${event.request.method} ${event.url.pathname}`);
  return resolve(event);
};

export const handle = sequence(uvbHandle, loggingHandle);

Server Utilities (@sp-uvb/sveltekit/server)

getUvbSession(event)

Get session from request event (returns undefined if no session).

import { getUvbSession } from '@sp-uvb/sveltekit/server';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async (event) => {
  const session = getUvbSession(event);

  if (!session) {
    return new Response('Not authenticated', { status: 401 });
  }

  return json({ userId: session.userId });
};

requireUvbSession(event)

Require session or throw 401 error.

import { requireUvbSession } from '@sp-uvb/sveltekit/server';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  const session = requireUvbSession(event);
  // If we get here, user is authenticated

  return { userId: session.userId };
};

requireFactors(event, factors)

Require specific MFA factors or throw 403 error.

import { requireFactors } from '@sp-uvb/sveltekit/server';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async (event) => {
  // Require both TOTP and WebAuthn
  const session = requireFactors(event, ['totp', 'webauthn']);

  // Perform admin action
  return json({ success: true });
};

hasFactor(event, factor)

Check if user has specific factor (returns boolean).

import { hasFactor } from '@sp-uvb/sveltekit/server';

export const load: PageServerLoad = async (event) => {
  const hasTotp = hasFactor(event, 'totp');

  return { totpEnabled: hasTotp };
};

isOwner(event, resourceUserId)

Check if current user owns a resource.

import { isOwner } from '@sp-uvb/sveltekit/server';

export const GET: RequestHandler = async ({ params, ...event }) => {
  const post = await getPost(params.id);

  if (!isOwner(event, post.userId)) {
    return new Response('Forbidden', { status: 403 });
  }

  return json(post);
};

requireOwnership(event, resourceUserId)

Require ownership or throw 403 error.

import { requireOwnership } from '@sp-uvb/sveltekit/server';

export const DELETE: RequestHandler = async ({ params, ...event }) => {
  const post = await getPost(params.id);
  requireOwnership(event, post.userId);

  await deletePost(params.id);
  return json({ success: true });
};

Client Stores (@sp-uvb/sveltekit/client)

uvbSession

Writable store containing the current session.

<script lang="ts">
  import { uvbSession } from '@sp-uvb/sveltekit/client'
</script>

{#if $uvbSession}
  <p>User ID: {$uvbSession.userId}</p>
  <p>Factors: {$uvbSession.factorsVerified.join(', ')}</p>
{/if}

isAuthenticated

Derived store indicating if user is authenticated.

<script lang="ts">
  import { isAuthenticated } from '@sp-uvb/sveltekit/client'
</script>

{#if $isAuthenticated}
  <a href="/dashboard">Dashboard</a>
{:else}
  <a href="/login">Login</a>
{/if}

hasFactor(factor)

Create a derived store checking for a specific factor.

<script lang="ts">
  import { hasFactor } from '@sp-uvb/sveltekit/client'

  const totpEnabled = hasFactor('totp')
  const webauthnEnabled = hasFactor('webauthn')
</script>

{#if $totpEnabled}
  <span>✓ TOTP Enabled</span>
{/if}

{#if $webauthnEnabled}
  <span>✓ WebAuthn Enabled</span>
{/if}

hasAllFactors(factors)

Create a derived store checking if user has all specified factors.

<script lang="ts">
  import { hasAllFactors } from '@sp-uvb/sveltekit/client'

  const hasStrongAuth = hasAllFactors(['totp', 'webauthn'])
</script>

{#if $hasStrongAuth}
  <button on:click={performSensitiveAction}>Delete Account</button>
{:else}
  <p>Enable TOTP and WebAuthn to access this feature</p>
{/if}

hasAnyFactor(factors)

Create a derived store checking if user has any of the specified factors.

<script lang="ts">
  import { hasAnyFactor } from '@sp-uvb/sveltekit/client'

  const hasMfa = hasAnyFactor(['totp', 'webauthn', 'sms'])
</script>

{#if $hasMfa}
  <span class="badge">MFA Enabled</span>
{/if}

Complete Examples

Protected Page with Load Function

// src/routes/dashboard/+page.server.ts
import { requireUvbSession } from '@sp-uvb/sveltekit/server';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  const session = requireUvbSession(event);

  // Fetch user data
  const user = await db.users.findUnique({
    where: { id: session.userId },
  });

  return {
    user,
    uvbSession: session, // Pass to client
  };
};
<!-- src/routes/dashboard/+page.svelte -->
<script lang="ts">
  import { onMount } from 'svelte'
  import { uvbSession } from '@sp-uvb/sveltekit/client'
  import type { PageData } from './$types'

  export let data: PageData

  onMount(() => {
    $uvbSession = data.uvbSession
  })
</script>

<h1>Dashboard</h1>
<p>Welcome, {data.user.name}</p>
<p>User ID: {$uvbSession?.userId}</p>

API Route with Factor Requirements

// src/routes/api/admin/users/+server.ts
import { requireFactors } from '@sp-uvb/sveltekit/server';
import { json, error as svelteError } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async (event) => {
  const session = requireFactors(event, ['totp', 'webauthn']);

  const users = await db.users.findMany();

  return json({ users });
};

export const POST: RequestHandler = async (event) => {
  const session = requireFactors(event, ['totp', 'webauthn']);
  const body = await event.request.json();

  // Validate input
  if (!body.email || !body.name) {
    throw svelteError(400, 'Email and name are required');
  }

  const user = await db.users.create({
    data: {
      email: body.email,
      name: body.name,
      createdBy: session.userId,
    },
  });

  return json({ user }, { status: 201 });
};

Resource Ownership Protection

// src/routes/api/posts/[id]/+server.ts
import { requireOwnership, requireUvbSession } from '@sp-uvb/sveltekit/server';
import { json, error as svelteError } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ params }) => {
  const post = await db.posts.findUnique({
    where: { id: params.id },
  });

  if (!post) {
    throw svelteError(404, 'Post not found');
  }

  return json({ post });
};

export const PATCH: RequestHandler = async ({ params, request, ...event }) => {
  const post = await db.posts.findUnique({
    where: { id: params.id },
  });

  if (!post) {
    throw svelteError(404, 'Post not found');
  }

  // Ensure user owns the post
  requireOwnership(event, post.userId);

  const body = await request.json();
  const updated = await db.posts.update({
    where: { id: params.id },
    data: body,
  });

  return json({ post: updated });
};

export const DELETE: RequestHandler = async ({ params, ...event }) => {
  const post = await db.posts.findUnique({
    where: { id: params.id },
  });

  if (!post) {
    throw svelteError(404, 'Post not found');
  }

  requireOwnership(event, post.userId);

  await db.posts.delete({
    where: { id: params.id },
  });

  return json({ success: true });
};

Conditional MFA Requirements

// src/routes/api/transfer/+server.ts
import { requireUvbSession, requireFactors } from '@sp-uvb/sveltekit/server';
import { json, error as svelteError } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async (event) => {
  const session = requireUvbSession(event);
  const body = await event.request.json();

  if (!body.amount || !body.recipient) {
    throw svelteError(400, 'Amount and recipient are required');
  }

  // Require stronger auth for larger amounts
  if (body.amount > 10000) {
    requireFactors(event, ['totp', 'webauthn']);
  } else if (body.amount > 1000) {
    requireFactors(event, ['totp']);
  }

  // Process transfer
  const transfer = await processTransfer({
    from: session.userId,
    to: body.recipient,
    amount: body.amount,
  });

  return json({ transfer });
};

Form Actions with Authentication

// src/routes/settings/+page.server.ts
import { requireUvbSession, requireFactors } from '@sp-uvb/sveltekit/server';
import { fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  const session = requireUvbSession(event);

  const user = await db.users.findUnique({
    where: { id: session.userId },
  });

  return { user, uvbSession: session };
};

export const actions = {
  updateProfile: async (event) => {
    const session = requireUvbSession(event);
    const formData = await event.request.formData();

    const name = formData.get('name');
    const email = formData.get('email');

    if (!name || !email) {
      return fail(400, { error: 'Name and email are required' });
    }

    await db.users.update({
      where: { id: session.userId },
      data: { name: name.toString(), email: email.toString() },
    });

    return { success: true };
  },

  deleteAccount: async (event) => {
    // Require strong MFA for account deletion
    const session = requireFactors(event, ['totp', 'webauthn']);

    await db.users.delete({
      where: { id: session.userId },
    });

    return { success: true };
  },
} satisfies Actions;

Client-Side Factor Checking

<!-- src/routes/settings/security/+page.svelte -->
<script lang="ts">
  import { hasFactor, hasAllFactors } from '@sp-uvb/sveltekit/client'
  import type { PageData } from './$types'

  export let data: PageData

  const totpEnabled = hasFactor('totp')
  const webauthnEnabled = hasFactor('webauthn')
  const strongAuth = hasAllFactors(['totp', 'webauthn'])
</script>

<h1>Security Settings</h1>

<section>
  <h2>Two-Factor Authentication</h2>

  <div class="factor">
    <h3>TOTP Authenticator</h3>
    {#if $totpEnabled}
      <span class="badge success">✓ Enabled</span>
      <form method="POST" action="?/disableTotp">
        <button type="submit">Disable</button>
      </form>
    {:else}
      <span class="badge">Not Enabled</span>
      <a href="/settings/security/totp/setup">Enable TOTP</a>
    {/if}
  </div>

  <div class="factor">
    <h3>WebAuthn (Security Key)</h3>
    {#if $webauthnEnabled}
      <span class="badge success">✓ Enabled</span>
      <form method="POST" action="?/disableWebauthn">
        <button type="submit">Manage Keys</button>
      </form>
    {:else}
      <span class="badge">Not Enabled</span>
      <a href="/settings/security/webauthn/setup">Add Security Key</a>
    {/if}
  </div>
</section>

{#if $strongAuth}
  <section class="admin-features">
    <h2>Advanced Features</h2>
    <p>You have strong authentication enabled. You can now:</p>
    <ul>
      <li><a href="/settings/api-keys">Manage API Keys</a></li>
      <li><a href="/settings/account">Delete Account</a></li>
    </ul>
  </section>
{/if}

Layout with Session Initialization

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { onMount } from 'svelte'
  import { uvbSession } from '@sp-uvb/sveltekit/client'
  import type { LayoutData } from './$types'

  export let data: LayoutData

  onMount(() => {
    if (data.uvbSession) {
      $uvbSession = data.uvbSession
    }
  })
</script>

<slot />
// src/routes/+layout.server.ts
import { getUvbSession } from '@sp-uvb/sveltekit/server';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async (event) => {
  const session = getUvbSession(event);

  return {
    uvbSession: session, // Make available to all pages
  };
};

Environment Variables

# .env
VITE_UVB_TENANT_ID=your-tenant-id
VITE_UVB_URL=http://localhost:8080

# Server-only (not prefixed with VITE_)
UVB_API_KEY=your-api-key

Access in code:

// Client-side (vite env)
import { createUvbHandle } from '@sp-uvb/sveltekit/hooks';

export const handle = createUvbHandle({
  tenantId: import.meta.env.VITE_UVB_TENANT_ID,
  uvbUrl: import.meta.env.VITE_UVB_URL,
});

// Server-side (node env)
import { env } from '$env/dynamic/private';

export const handle = createUvbHandle({
  tenantId: env.UVB_TENANT_ID!,
  apiKey: env.UVB_API_KEY,
});

TypeScript

Full TypeScript support with type exports:

import type { UvbSession, UvbHandleOptions } from '@sp-uvb/sveltekit';

// Session is also available in App.Locals
declare global {
  namespace App {
    interface Locals {
      uvbSession?: UvbSession;
    }
  }
}

Error Handling

All server utilities throw SvelteKit errors:

import { requireUvbSession } from '@sp-uvb/sveltekit/server';
import { error } from '@sveltejs/kit';

export const load: PageServerLoad = async (event) => {
  try {
    const session = requireUvbSession(event);
    return { userId: session.userId };
  } catch (err) {
    // requireUvbSession throws error(401, ...)
    // This will be caught by SvelteKit's error handling
    throw err;
  }
};

License

MIT