Add Auth to a SvelteKit App in 5 Minutes

13 Mar 2026 · Bank K.

Set up cookie-based authentication in your SvelteKit app with sessions, protected routes, and login forms. Step-by-step code included.

You picked SvelteKit for your next project. The landing page is up, the database is connected, and now you need login and signup before anyone can use it. The problem: SvelteKit’s auth story is fragmented. Lucia was deprecated. Auth.js works but feels like overkill for a side project. Rolling your own session system means writing cookie logic, password hashing, and protected route guards from scratch.

Here’s a faster path. This guide walks through adding cookie-based authentication to a SvelteKit app — from signup form to protected dashboard — using the least amount of code that actually works in production.

What You’re Building

By the end of this tutorial, your SvelteKit app will have:

  • A signup page that creates user accounts with hashed passwords
  • A login page that sets a secure session cookie
  • A server hook that validates sessions on every request
  • Protected routes that redirect unauthenticated users to login

If you want full control, here’s the minimal setup.

Install Dependencies

npm install bcrypt uuid

bcrypt handles password hashing. uuid generates session tokens. You’ll also need a database — Prisma, Drizzle, or even a simple JSON file works for prototyping.

Create the Auth Helper

// src/lib/server/auth.ts
import bcrypt from 'bcrypt';
import { v4 as uuid } from 'uuid';
import { db } from './db';

export async function createUser(email: string, password: string) {
  const hash = await bcrypt.hash(password, 10);
  return db.user.create({ data: { email, passwordHash: hash } });
}

export async function verifyPassword(email: string, password: string) {
  const user = await db.user.findUnique({ where: { email } });
  if (!user) return null;
  const valid = await bcrypt.compare(password, user.passwordHash);
  return valid ? user : null;
}

export async function createSession(userId: string) {
  const token = uuid();
  await db.session.create({
    data: { token, userId, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) }
  });
  return token;
}

export async function validateSession(token: string) {
  const session = await db.session.findUnique({
    where: { token },
    include: { user: true }
  });
  if (!session || session.expiresAt < new Date()) return null;
  return session.user;
}

Add the Server Hook

SvelteKit hooks run on every request. This is where you check the session cookie and attach the user to event.locals.

// src/hooks.server.ts
import { validateSession } from '$lib/server/auth';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const sessionToken = event.cookies.get('session');

  if (sessionToken) {
    const user = await validateSession(sessionToken);
    if (user) {
      event.locals.user = user;
    } else {
      event.cookies.delete('session', { path: '/' });
    }
  }

  return resolve(event);
};

Build the Login Action

// src/routes/login/+page.server.ts
import { verifyPassword, createSession } from '$lib/server/auth';
import { fail, redirect } from '@sveltejs/kit';

export const actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email') as string;
    const password = data.get('password') as string;

    const user = await verifyPassword(email, password);
    if (!user) return fail(400, { error: 'Invalid email or password' });

    const token = await createSession(user.id);
    cookies.set('session', token, {
      path: '/',
      httpOnly: true,
      sameSite: 'lax',
      secure: true,
      maxAge: 60 * 60 * 24 * 7 // 7 days
    });

    throw redirect(303, '/dashboard');
  }
};

Protect Routes

In any +page.server.ts or +layout.server.ts, check for the user:

// src/routes/dashboard/+layout.server.ts
import { redirect } from '@sveltejs/kit';

export const load = async ({ locals }) => {
  if (!locals.user) throw redirect(303, '/login');
  return { user: locals.user };
};

That’s it. Every page under /dashboard now requires authentication.

Option 2: Skip the Boilerplate with Beag

Building auth from scratch teaches you how sessions work. But if you’re shipping a product — not learning — you probably don’t want to maintain password reset flows, email verification, rate limiting, and session cleanup yourself.

Beag gives you auth + Stripe payments as a drop-in package. One install, and your SvelteKit app gets login, signup, password reset, and a billing portal. The setup takes about 5 minutes:

npx beag init --framework sveltekit

This scaffolds the auth routes, hooks, and Stripe webhook handler. You get a working /login, /signup, /dashboard, and /billing page out of the box. No session management code to write, no payment integration to debug.

If you already built the auth layer yourself using the approach above, Beag can still handle just the payments side — check the docs for the Stripe-only setup.

Common Mistakes to Avoid

Storing passwords in plain text. Always hash with bcrypt or argon2. Never store raw passwords, even during prototyping.

Forgetting httpOnly on cookies. Without this flag, any XSS vulnerability on your site exposes session tokens. SvelteKit defaults to httpOnly: true, but verify your cookie settings explicitly.

Not setting path: '/'. SvelteKit v2 requires the path option when setting cookies. Without it, your cookie scope might be limited to the current route, and users will appear logged out when they navigate.

Skipping CSRF protection. SvelteKit form actions include built-in CSRF protection. If you’re using custom API routes instead, add CSRF tokens manually.

No session expiration. Sessions without expiration dates live forever. Set a reasonable maxAge (7 days is standard) and clean up expired sessions from your database periodically.

SvelteKit Auth Libraries in 2026

The landscape has shifted since Lucia’s deprecation in 2025. Here’s what’s available now:

LibraryApproachBest For
Auth.js (SvelteKit)OAuth providersApps needing Google/GitHub login
Better AuthFull-featuredProduction apps with complex auth needs
Custom (this guide)Sessions + cookiesSimple apps, learning, full control
BeagAuth + payments bundleMVPs and indie hacker projects

For most side projects and MVPs, the custom approach in this guide covers 90% of what you need. When you’re ready to add OAuth providers or payment gating, swap in a more complete solution.

FAQ

Is SvelteKit good for building SaaS apps?

Yes. SvelteKit handles server-side rendering, API routes, and form actions natively. Combined with its small bundle size and fast hydration, it’s one of the best frameworks for building full-stack SaaS products as a solo developer.

What replaced Lucia auth in SvelteKit?

Lucia was deprecated in March 2025. The Lucia maintainers now recommend implementing session-based auth directly (as shown in this guide) or using Better Auth, which has first-class SvelteKit support. Auth.js also supports SvelteKit for OAuth-based flows.

How do I protect API routes in SvelteKit?

Check event.locals.user in your +server.ts files. If the user isn’t set by your server hook, return a 401 response. For API routes that need authentication, add the check at the top of your GET/POST handlers.

Can I add Stripe payments to SvelteKit?

Yes. You can integrate Stripe directly using their Node.js SDK, or use Beag to get auth and Stripe billing pre-configured for SvelteKit. Beag handles webhook verification, subscription management, and the billing portal so you can focus on your product.

About the Author
Bank K.

Bank K.

Serial entrepreneur & Co-founder of Beag.io

Founder of Beag.io. Indie hacker building tools to help developers ship faster.

Ready to Make Money From Your SaaS?

Turn your SaaS into cash with Beag.io. Get started now!

Start 7-day free trial →