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

convex-auth-sveltekit

v0.1.0

Published

Convex Auth integration for SvelteKit — server-side token management, automatic refresh, and route protection

Readme

convex-auth-sveltekit

Convex Auth integration for SvelteKit. Server-side token management, automatic JWT refresh, and route protection — matching what @convex-dev/auth/nextjs provides for Next.js.

What this does

  • Keeps refresh tokens server-side — stored in httpOnly cookies, never exposed to client JavaScript
  • Automatic token refresh — refreshes JWTs before they expire on every server request
  • Auth proxy — routes signIn/signOut through your server so the real refresh token never leaves the cookie
  • Route protection — composable route matching for protected/public paths
  • Client WebSocket auth — wires the Convex WebSocket to server-managed tokens

Install

npm install convex-auth-sveltekit

Peer dependencies

npm install convex @convex-dev/auth @sveltejs/kit svelte

Setup

1. Type definitions

Add the auth locals to your src/app.d.ts:

declare global {
  namespace App {
    interface Locals {
      token: string | null;
      isAuthenticated: boolean;
    }
  }
}

export {};

2. Server hook

Create or update src/hooks.server.ts:

import { sequence } from "@sveltejs/kit/hooks";
import { redirect } from "@sveltejs/kit";
import type { Handle } from "@sveltejs/kit";
import {
  createConvexAuthHandle,
  createRouteMatcher,
} from "convex-auth-sveltekit/server";
import { CONVEX_URL } from "$env/static/private";

// Handles token refresh and populates event.locals.
const convexAuth = createConvexAuthHandle({ convexUrl: CONVEX_URL });

// Your custom route protection.
const isProtectedRoute = createRouteMatcher(["/dashboard", "/app", "/settings"]);
const isAuthRoute = createRouteMatcher(["/auth"]);

const routeProtection: Handle = async ({ event, resolve }) => {
  if (isProtectedRoute(event.url.pathname) && !event.locals.isAuthenticated) {
    throw redirect(303, "/auth/login");
  }
  if (isAuthRoute(event.url.pathname) && event.locals.isAuthenticated) {
    throw redirect(303, "/dashboard");
  }
  return resolve(event);
};

export const handle = sequence(convexAuth, routeProtection);

3. Auth proxy endpoint

Create src/routes/api/auth/+server.ts:

import { createAuthProxy } from "convex-auth-sveltekit/server";
import { CONVEX_URL } from "$env/static/private";

export const POST = createAuthProxy({ convexUrl: CONVEX_URL });

4. Token endpoint

Create src/routes/api/auth/token/+server.ts (or wherever fits your route structure):

import { createTokenEndpoint } from "convex-auth-sveltekit/server";

export const GET = createTokenEndpoint();

5. Client-side setup

In your authenticated layout (e.g. src/routes/(app)/+layout.svelte):

<script lang="ts">
  import { setupConvex, useConvexClient } from "convex-svelte";
  import { setupConvexAuth } from "convex-auth-sveltekit/client";
  import { browser } from "$app/environment";
  import { PUBLIC_CONVEX_URL } from "$env/static/public";
  import { setContext } from "svelte";

  let { data, children } = $props();

  setupConvex(PUBLIC_CONVEX_URL);
  const client = useConvexClient();

  let isAuthed = $state(false);
  setContext("convexAuth", { get isAuthed() { return isAuthed; } });

  if (browser) {
    setupConvexAuth(client, {
      onAuthChange: (authenticated) => { isAuthed = authenticated; },
    });
  }
</script>

{@render children()}

6. Server-side data loading

In +page.server.ts or +layout.server.ts:

import { getConvexClient } from "convex-auth-sveltekit/server";
import { api } from "$convex/_generated/api";
import { CONVEX_URL } from "$env/static/private";

export const load = async ({ locals }) => {
  const client = getConvexClient(CONVEX_URL, locals.token);
  const user = await client.query(api.src.users.queries.getCurrentUser, {});
  return { user };
};

7. Sign-in with form actions

In src/routes/auth/login/+page.server.ts:

import { proxySignIn } from "convex-auth-sveltekit/server";
import { fail, redirect } from "@sveltejs/kit";
import { CONVEX_URL } from "$env/static/private";

export const actions = {
  sendCode: async ({ request, cookies, url }) => {
    const formData = await request.formData();
    const host = url.hostname;

    const result = await proxySignIn(
      { provider: "resend-otp", params: { email: formData.get("email") } },
      cookies,
      host,
      CONVEX_URL,
    );

    if (result.error) return fail(400, { error: result.error });
    return { codeSent: true };
  },

  verifyCode: async ({ request, cookies, url }) => {
    const formData = await request.formData();
    const host = url.hostname;

    const result = await proxySignIn(
      {
        provider: "resend-otp",
        params: {
          email: formData.get("email"),
          code: formData.get("code"),
        },
      },
      cookies,
      host,
      CONVEX_URL,
    );

    if (result.error) return fail(400, { error: result.error });
    if (result.token) throw redirect(303, "/dashboard");
    return fail(400, { error: "Verification failed." });
  },
};

8. Sign-out

<script lang="ts">
  import { signOut } from "convex-auth-sveltekit/client";
</script>

<button onclick={() => signOut()}>Log out</button>

API Reference

Server (convex-auth-sveltekit/server)

| Export | Description | |--------|-------------| | createConvexAuthHandle(config) | SvelteKit handle hook — refreshes tokens, populates locals | | createAuthProxy(config) | POST handler factory for the auth proxy endpoint | | createTokenEndpoint() | GET handler factory that returns the current JWT | | createRouteMatcher(patterns) | Creates a path-matching function for route protection | | getConvexClient(convexUrl, token?) | Creates a ConvexHttpClient, optionally authenticated with a JWT | | proxySignIn(args, cookies, host, convexUrl) | Low-level signIn proxy for form actions | | proxySignOut(cookies, host, convexUrl) | Low-level signOut proxy | | getAuthTokens(cookies, host) | Reads JWT and refresh token from cookies | | setAuthCookies(cookies, tokens, host) | Sets or clears auth cookies | | refreshTokensIfNeeded(cookies, host, convexUrl) | Refreshes JWT if expiring soon | | extractErrorMessage(raw) | Extracts user-friendly message from Convex errors |

Client (convex-auth-sveltekit/client)

| Export | Description | |--------|-------------| | setupConvexAuth(client, options?) | Wires a Convex client to server-managed auth tokens | | signOut(options?) | Signs out via the auth proxy and redirects | | signIn(args, options?) | Sends a signIn request through the auth proxy |

How it works

  1. On every request, the handle hook decodes the JWT from the cookie, checks if it's expiring soon, and refreshes it via Convex if needed. The new tokens are set back as cookies.

  2. Sign-in/sign-out go through the /api/auth proxy. The browser sends a dummy refresh token; the proxy swaps it with the real one from the httpOnly cookie before forwarding to Convex. The response splits the tokens: JWT goes to the client, refresh token stays in the cookie.

  3. The client Convex WebSocket authenticates by fetching the JWT from /api/auth/token. It never sees the refresh token.

License

MIT