PasskeyBase

Svelte SDK

The @passkeybase/svelte package provides Svelte stores, actions, and SvelteKit server hooks for integrating passkey authentication into Svelte applications.


Installation

Terminal
npm install @passkeybase/svelte @passkeybase/js

Initialization

Call initPasskeyBase() in your root layout to initialize the SDK. This sets up the internal stores that all child components can access viagetPasskeyBase().

+layout.svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { initPasskeyBase } from '@passkeybase/svelte';

  initPasskeyBase({
    clientId: 'pk_live_abc123',
    domain: 'myapp.auth.ing',
    redirectUri: window.location.origin + '/callback',
  });
</script>

<slot />

Auth Stores

getPasskeyBase()

Returns reactive Svelte stores for auth state, the current user, and methods for login, logout, and token access. Use the $ prefix for auto-subscription in templates.

dashboard/+page.svelte
<!-- src/routes/dashboard/+page.svelte -->
<script lang="ts">
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { isAuthenticated, isLoading, loginWithRedirect, logout, getAccessToken } =
    getPasskeyBase();

  async function fetchData() {
    const token = await getAccessToken();
    const res = await fetch('/api/data', {
      headers: { Authorization: `Bearer ${token}` },
    });
    return res.json();
  }
</script>

{#if $isLoading}
  <div>Loading...</div>
{:else if !$isAuthenticated}
  <button on:click={() => loginWithRedirect()}>Log in with Passkey</button>
{:else}
  <h1>Welcome back</h1>
  <button on:click={fetchData}>Fetch data</button>
  <button on:click={() => logout()}>Log out</button>
{/if}

User Store

Access the current user's profile as a reactive store.

profile/+page.svelte
<!-- src/routes/profile/+page.svelte -->
<script lang="ts">
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { user, isLoading } = getPasskeyBase();
</script>

{#if $isLoading}
  <div>Loading profile...</div>
{:else if $user}
  <h2>{$user.displayName}</h2>
  <p>{$user.email}</p>
  <p>User ID: {$user.sub}</p>
  <p>Passkeys registered: {$user.credentials.length}</p>
{/if}

Protected Routes

Client-Side Guard

Create a layout component that checks auth state and redirects unauthenticated users.

dashboard/+layout.svelte
<!-- src/routes/dashboard/+layout.svelte -->
<script lang="ts">
  import { getPasskeyBase } from '@passkeybase/svelte';
  import { goto } from '$app/navigation';
  import { onMount } from 'svelte';

  const { isAuthenticated, isLoading, loginWithRedirect } = getPasskeyBase();

  // Redirect unauthenticated users
  $: if (!$isLoading && !$isAuthenticated) {
    loginWithRedirect({ state: window.location.pathname });
  }
</script>

{#if $isLoading}
  <div>Checking authentication...</div>
{:else if $isAuthenticated}
  <slot />
{/if}

SvelteKit Server Hook

Use the handle hook from@passkeybase/svelte/server to protect routes at the server level. It populates locals.session for use in load functions.

hooks.server.ts
// src/hooks.server.ts
import { handle as passkeyBaseHandle } from '@passkeybase/svelte/server';

export const handle = passkeyBaseHandle({
  domain: 'myapp.auth.ing',
  clientId: 'pk_live_abc123',
  protectedRoutes: ['/dashboard', '/settings'],
  loginRoute: '/',
});

Chain with other hooks using sequence:

hooks.server.ts (chained)
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { handle as passkeyBaseHandle } from '@passkeybase/svelte/server';

const auth = passkeyBaseHandle({
  domain: 'myapp.auth.ing',
  clientId: 'pk_live_abc123',
  protectedRoutes: ['/dashboard', '/settings'],
});

const logger: Handle = async ({ event, resolve }) => {
  console.log(`${event.request.method} ${event.url.pathname}`);
  return resolve(event);
};

export const handle = sequence(auth, logger);

Access the session in server load functions:

+page.server.ts
// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  // locals.session is set by the handle hook
  const session = locals.session;

  if (!session) {
    return { status: 401 };
  }

  return {
    user: session.user,
  };
};

Login and Logout

Svelte Actions

Use the use:login anduse:logout actions for declarative login/logout buttons.

AuthButtons.svelte
<!-- src/lib/components/AuthButtons.svelte -->
<script lang="ts">
  import { login, logout as logoutAction } from '@passkeybase/svelte';
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { isAuthenticated } = getPasskeyBase();
</script>

{#if $isAuthenticated}
  <button use:logoutAction={{ returnTo: window.location.origin }}>
    Sign out
  </button>
{:else}
  <button use:login>
    Sign in with Passkey
  </button>
{/if}

Programmatic Login

Use loginWithRedirect() andlogout() for full control.

CustomLogin.svelte
<script lang="ts">
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { loginWithRedirect, logout } = getPasskeyBase();

  function handleLogin() {
    loginWithRedirect({
      scopes: ['openid', 'profile', 'email'],
      state: 'return=/onboarding',
    });
  }

  function handleLogout() {
    logout({ returnTo: window.location.origin });
  }
</script>

<button on:click={handleLogin}>Log in</button>
<button on:click={handleLogout}>Log out</button>

Handling the Callback

Create a callback page to finalize the OAuth redirect flow.

callback/+page.svelte
<!-- src/routes/callback/+page.svelte -->
<script lang="ts">
  import { onMount } from 'svelte';
  import { goto } from '$app/navigation';
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { handleRedirectCallback } = getPasskeyBase();

  onMount(async () => {
    const result = await handleRedirectCallback();
    const returnTo = result.state || '/dashboard';
    goto(returnTo);
  });
</script>

<div>Completing login...</div>

Passkey Enrollment

Use enrollPasskey() from the store to open the enrollment popup. Fetch the enrollment token from your server first.

settings/passkeys/+page.svelte
<!-- src/routes/settings/passkeys/+page.svelte -->
<script lang="ts">
  import { getPasskeyBase } from '@passkeybase/svelte';

  const { getAccessToken, enrollPasskey } = getPasskeyBase();

  let isEnrolling = false;
  let error: string | null = null;

  async function handleEnroll() {
    isEnrolling = true;
    error = null;

    try {
      const token = await getAccessToken();
      const res = await fetch('/api/enrollment-token', {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
      });
      const { enrollmentToken } = await res.json();

      await enrollPasskey(enrollmentToken);
      alert('Passkey added successfully!');
    } catch (err: any) {
      error = err.message;
    } finally {
      isEnrolling = false;
    }
  }
</script>

<h2>Passkey Management</h2>
<button on:click={handleEnroll} disabled={isEnrolling}>
  {isEnrolling ? 'Enrolling...' : 'Add a new passkey'}
</button>
{#if error}
  <p class="error">{error}</p>
{/if}

TypeScript Support

All stores, actions, and server utilities are fully typed. The package uses Svelte'sReadable store types and re-exports types from @passkeybase/js.

TypeScript types
import type { User, AuthResult } from '@passkeybase/js';
import type { PasskeyBaseStore } from '@passkeybase/svelte';

// getPasskeyBase() returns typed Svelte stores
const { user, isAuthenticated } = getPasskeyBase();

// $user       — User | undefined (auto-subscribed)
// $isAuthenticated — boolean (auto-subscribed)

// Init options are typed
import type { PasskeyBaseConfig } from '@passkeybase/svelte';

const config: PasskeyBaseConfig = {
  clientId: 'pk_live_abc123',
  domain: 'myapp.auth.ing',
  redirectUri: '/callback',
};

Next steps