How to Add Authentication to Your Web App with Better Auth?

Chris ChenChris Chen
10 min read
Dec 29, 2025

sveltekit & better auth

Authentication is a core part of almost every web application. Whether you're building a SaaS product, a personal project, or an internal tool, chances are you'll need users to sign in.

But implementing auth from scratch is tricky. You have to deal with secure password hashing, session management, OAuth flows, token refresh, and a dozen other things that can go wrong. Auth is important, but it's not the feature that makes your app unique. You probably want to spend your time elsewhere.

That's where Better Auth comes in.

Better Auth is a modern, TypeScript-first authentication library designed to simplify authentication in web applications.

It focuses on flexibility, portability, and a developer-friendly API:

  • Framework-agnostic: Can be integrated with frameworks such as SvelteKit, Next.js, Nuxt, Express, and others
  • Built-in social providers: Includes support for common OAuth providers like Google, GitHub, Discord, and Twitter
  • Simple API: Designed to keep configuration minimal for common authentication scenarios
  • No vendor lock-in: Database-agnostic by design and deployable across different platforms

In this guide, I'll walk you through integrating Better Auth with SvelteKit. By the end, you'll have a working authentication system with social login.

Integrating with SvelteKit

1. Installation & Setup

First, create a new SvelteKit project (or use an existing one):

npx sv create my-app
cd my-app

Install Better Auth:

npm install better-auth

Next, set up your environment variables. Create a  .env  file in your project root:

# Better Auth Secret Key
BETTER_AUTH_SECRET=your-better-auth-secret

# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

To generate  BETTER_AUTH_SECRET , run this command in your terminal:

openssl rand -base64 32

To get OAuth credentials:

Set the callback URL to  http://localhost:5173/api/auth/callback/github  (or  /google  for Google).

2. Server Configuration

Create the auth instance on the server. This is where you define your providers and settings.

Create  src/lib/server/auth.ts :

import { betterAuth } from 'better-auth';
import { env } from '$env/dynamic/private';

export const auth = betterAuth({
	secret: env.BETTER_AUTH_SECRET,
	socialProviders: {
		github: {
			clientId: env.GITHUB_CLIENT_ID as string,
			clientSecret: env.GITHUB_CLIENT_SECRET as string
		},
		google: {
			clientId: env.GOOGLE_CLIENT_ID as string,
			clientSecret: env.GOOGLE_CLIENT_SECRET as string
		}
	}
});

Now create the API route handler. Better Auth uses a catch-all route to handle all auth-related requests.

Create  src/routes/api/auth/[...all]/+server.ts :

import { auth } from '$lib/server/auth';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ request }) => {
    return auth.handler(request);
};

export const POST: RequestHandler = async ({ request }) => {
    return auth.handler(request);
};

That's it for the server side. Better Auth now handles  /api/auth/*  routes automatically.

3. Client Configuration

Create the auth client for use in your Svelte components.

Create  src/lib/auth-client.ts :

import { createAuthClient } from 'better-auth/svelte';

export const authClient = createAuthClient();

The client automatically detects your auth endpoint. You can now use  authClient  anywhere in your app to trigger sign in and sign out actions.

4. Social Login Implementation

First, create  src/hooks.server.ts  to get the user session on every request:

import { auth } from '$lib/server/auth';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
    const session = await auth.api.getSession({
        headers: event.request.headers
    });

    event.locals.session = session;

    return resolve(event);
};

Add the type definition for  locals . Update  src/app.d.ts :

import type { auth } from '$lib/server/auth';

declare global {
	namespace App {
		interface Locals {
			session: typeof auth.$Infer.Session | null;
		}
	}
}

export {};

Now create a layout server load function to pass session data to all pages.

Create  src/routes/+layout.server.ts :

import type { LayoutServerLoad } from './$types';

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

Build the login page. Create  src/routes/login/+page.svelte :

<script lang="ts">
    import { authClient } from '$lib/auth-client';

    const signInWithGitHub = async () => {
        await authClient.signIn.social({
            provider: 'github',
            callbackURL: '/'
        });
    };
    
    const signInWithGoogle = async () => {
        await authClient.signIn.social({
            provider: 'google',
            callbackURL: '/'
        });
    };
</script>

<div class="login-container">
    <h1>Sign In</h1>
    <button onclick={signInWithGitHub}>Continue with GitHub</button>
    <button onclick={signInWithGoogle}>Continue with Google</button>
</div>

For displaying user info and handling sign out, update your layout component. Edit  src/routes/+layout.svelte :

<script lang="ts">
    import { authClient } from '$lib/auth-client';

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

    const signOut = async () => {
        await authClient.signOut();
    };
</script>

<header>
    {#if data.session}
        <p>Welcome, {data.session.user.name}!</p>
        <button onclick={signOut}>Sign Out</button>
    {:else}
        <a href="/login">Sign In</a>
    {/if}
</header>

<main>
    {@render children()}
</main>

This approach is SSR friendly. The session data is available immediately on page load, with no flash of unauthenticated content.

5. Route Protection

To protect specific routes, update  src/hooks.server.ts  to check authentication before rendering a page:

import { auth } from '$lib/server/auth';
import { redirect, type Handle } from '@sveltejs/kit';

const protectedRoutes = ['/dashboard', '/settings', '/profile'];

export const handle: Handle = async ({ event, resolve }) => {
    const session = await auth.api.getSession({
        headers: event.request.headers
    });

    event.locals.session = session;

    const isProtected = protectedRoutes.some((route) => event.route.id?.startsWith(route));

    if (isProtected && !session) {
        throw redirect(303, '/login');
    }

    return resolve(event);
};

Now any route starting with  /dashboard /settings , or  /profile  will redirect unauthenticated users to the login page.

6. Database (Optional)

By default, Better Auth stores sessions in memory. This works fine for development, but you'll want a database for production to persist user data across server restarts.

Better Auth supports multiple database adapters including Prisma, Drizzle, and MongoDB.

Here's a quick example with Prisma:

import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: 'sqlite', // or mysql, postgresql
  }),
  socialProviders: {
    // ... your providers
  }
});

For full database setup instructions, check the Better Auth Database Documentation.

Quick Start Template

If you want to get started quickly, I've created a ready-to-use SvelteKit + Better Auth template with everything pre-configured:

  • GitHub, Google, Discord, Slack and Vercel OAuth ready
  • Session management with SSR support
  • Protected routes example
  • Environment variables template

👉 Deploy to EdgeOne Pages

Conclusion

Adding authentication doesn't have to be complicated. With Better Auth and SvelteKit, you can have a fully working social login system in under an hour.

Here's what we covered:

  • Setting up Better Auth in a SvelteKit project
  • Configuring Google and GitHub OAuth providers
  • Building sign-in/sign-out functionality
  • Protecting routes with server hooks
  • Optional database setup for production

If you want to get started even faster, grab the template and deploy it with one click.