Learn Svelte - Authentication & Authorization
Series/Learn Svelte/Episode 12
Episode 12 of 24

Learn Svelte - Authentication & Authorization

This episode covers the security of user identity: auth patterns in SvelteKit, session management and secure cookies, protected routes and route guards, and role-based access control and client-side and server-side auth.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

An app that holds user data must know who is using it. That identity determines what can be seen and changed. Without a proper auth layer, endpoints that should be private can be read by anyone.

Auth is two different things. Authentication verifies identity — who you are — while authorization determines access rights — what you're allowed to do. SvelteKit doesn't ship a built-in auth solution, but it provides all the primitives to build one: cookies, hooks, and load functions.

This episode covers auth patterns in SvelteKit, session management and secure cookies, protected routes and route guards, and role-based access control and client/server auth. When you're done, you can build a secure login system for a production app.

Auth Patterns in SvelteKit

Two Common Approaches

Unlike monolithic frameworks, SvelteKit leaves the choice of auth mechanism to you. The two most common patterns are session-based auth and token-based auth. The first stores a session on the server and sends a cookie containing a token, while the second carries a signed token in the Authorization header.

Session-based auth is simple, easy to revoke, and suitable for most apps. Token-based auth fits public APIs better, with the trade-off that tokens are hard to invalidate before they expire.

You can write it from scratch. Hash passwords using bcryptjs:

Install the hashing dependency
npm install bcryptjs

Server-Side Auth Flow

The most common flow in SvelteKit starts with a login form in a component, continues with a form action on the server, then a session cookie. The form action runs entirely on the server, so the password never reaches client JavaScript. This is the main reason SvelteKit recommends form actions for authentication.

Session Management and Secure Cookies

Cookies are the most practical session storage, as long as their security attributes are correct:

JSSet a secure session cookie
import { redirect } from "@sveltejs/kit"
 
export const actions = {
  async login({ request, cookies }) {
    const data = await request.formData()
    const email = data.get("email")
    const token = await buatToken(email)
    cookies.set("session", token, {
      httpOnly: true,
      secure: true,
      sameSite: "lax",
      path: "/",
      maxAge: 60 * 60 * 24 * 7,
    })
    throw redirect(303, "/dashboard")
  },
}

httpOnly: true makes the cookie unreadable by JavaScript, fending off XSS attacks that try to steal the session. secure: true forces the cookie to be sent over HTTPS only, and sameSite: "lax" protects against CSRF attacks. This combination is the minimum standard for auth cookies.

Hooks to Maintain User Context

handle in hooks.server.js runs before every request. This is where the token is turned into a user object accessible from every load function and form action:

JShooks.server.js - inject user into locals
import { verifikasiSession } from "$lib/server/session"
 
export async function handle({ event, resolve }) {
  const token = event.cookies.get("session")
  event.locals.user = token
    ? await verifikasiSession(token)
    : null
  return resolve(event)
}

event.locals.user is null when not logged in and a user object when the session is valid. With this pattern, the whole app reads identity from one source, without every page re-validating the token repeatedly.

Protected Routes and Route Guards

Guard at the Load Function Level

Pages that require login must not just hide in the UI. The guard must run on the server before the page renders:

JSRoute guard in +page.server.js
import { redirect } from "@sveltejs/kit"
 
export async function load({ locals }) {
  if (!locals.user) {
    throw redirect(303, "/masuk")
  }
  return { user: locals.user }
}

Throwing redirect in a load function is SvelteKit's idiomatic way to redirect users who aren't logged in. Because +page.server.js runs on the server, users can't bypass it through DevTools.

Guard at the Layout Level

When many pages need the same protection, put the guard in the parent's +layout.server.js. Once placed in a layout, all child routes are automatically protected. This pattern avoids code duplication between pages and makes access-policy changes easy.

Role-Based Access Control and Client/Server Auth

Checking Roles on the Server

Once the user is authenticated, authorization determines access rights based on role:

JSReusable role checker
import { error } from "@sveltejs/kit"
 
export function requireRole(user, role) {
  if (!user || !user.roles.includes(role)) {
    throw error(403, "Akses ditolak")
  }
}

user.roles.includes(role) checks whether the required role is in the user's list of roles. When it isn't, the function throws 403 Forbidden. This check should always happen on the server — a client can be tricked by tampering with the response.

Client-Side Auth

On the client, auth serves the experience: hiding admin buttons, showing the user's name, or redirecting to the login page:

Redirect a client that isn't logged in
<script>
  import { onMount } from "svelte"
  import { goto } from "$app/navigation"
 
  let { data } = $props()
 
  onMount(() => {
    if (!data.user) {
      goto("/masuk")
    }
  })
</script>

This client guard is for experience only, not security. Since it runs after the page loads, sensitive content must still be protected on the server. goto("/masuk") redirects the user without reloading the page.

Conclusion

Key takeaways:

  • Understand the difference between authentication (who) and authorization (what you may do).
  • Store sessions in cookies with httpOnly, secure, and sameSite.
  • Validate identity in hooks.server.js and inject it into event.locals.
  • Protect routes in +page.server.js or +layout.server.js with redirects.
  • Check roles on the server, not just in the UI.
  • Never send passwords or tokens to client JavaScript without a need.

Next, in episode 13 we will discuss secure data fetching — secure API consumption and token handling, CSRF, XSS, and input sanitization, and best practices for sensitive data. The session from this episode becomes the key that protects every API request.

Learn Svelte - Authentication & Authorization | Learn Svelte