Learn SvelteKit - Secure Data Fetching
Episode 13 of 24

Learn SvelteKit - Secure Data Fetching

This episode covers security in data fetching: securing API requests with auth headers, handling secrets and server-only config, CSRF and XSS protection, input sanitization, plus secure deployment configuration.

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

Introduction

After covering authentication in episode 12, episode 13 focuses on the security of fetching and sending data. This is the most commonly overlooked layer of defense: API requests that leak tokens, secrets embedded in the client bundle, and unsanitized input.

SvelteKit provides a clear boundary between server and client, but that boundary is only useful if it is used correctly. All sensitive data must stay on the server side, and every path where user data enters must be verified.

After this episode, you can write applications that hold secrets properly, reject suspicious requests, and sanitize input before storing or rendering it.

Secure API Requests and Auth Headers

Fetching with Auth Headers on the Server

External API calls that need a token are made from server load functions, not from the browser. The token is read from an environment variable and injected via the Authorization header, never visible to JavaScript on the client.

JSFetching an API with an auth header
export const load = async ({ fetch }) => {
    const res = await fetch("https://api.example.com/me", {
        headers: {
            Authorization: `Bearer ${process.env.API_TOKEN}`
        }
    });
 
    if (!res.ok) {
        throw error(502, "Gagal mengambil data dari layanan eksternal");
    }
 
    return { data: await res.json() };
};

Server load functions are never sent to the browser; only the returned values are serialized. Your tokens stay safe as long as you do not store secrets inside returned values.

Preventing Token Leaks to the Client

Inspect every value a load function returns. Do not return user objects containing passwordHash, apiKey, or raw tokens. Select only the fields the UI needs, or explicitly build a new object so the UI-facing shape does not drag internal data along.

JSSelecting safe fields for the client
export const load = async ({ locals }) => {
    const user = locals.user;
 
    return {
        profil: {
            id: user.id,
            nama: user.nama,
            avatar: user.avatar
        }
    };
};

Handling Secrets and Server-Only Config

Environment Variables with a Prefix

In SvelteKit, variables prefixed with PUBLIC_ can be accessed from the client via import.meta.env.PUBLIC_NAMA. All secrets must live in prefix-free environment variables and be read only on the server, for example through a module in src/lib/server.

JSServer-only config
const env = import.meta.env;
 
export const config = {
    databaseUrl: env.DATABASE_URL,
    apiToken: env.API_TOKEN
};
 
if (!config.databaseUrl || !config.apiToken) {
    throw new Error("Environment variable tidak lengkap");
}

Files in src/lib/server can only be imported from server code. Importing them from a client component fails at build time, so secrets hiding there cannot leak by accident.

Validating Environment on Startup

Validating the environment at startup prevents the application from running with a wrong configuration. The validation schema can be written by hand or built with a library like Zod. With npm install zod, you can check the presence and format of each variable once, then reuse the result across the server.

CSRF and XSS Protection

CSRF with sameSite and Origin Checks

The sameSite cookie attribute blocks most CSRF attacks, but for requests sent cross-origin, verifying the Origin header adds another layer of defense. Sensitive server actions should ensure the request origin matches the application origin.

JSVerifying origin in a server action
import { error } from "@sveltejs/kit";
 
export const actions = {
    ubahEmail: async ({ request }) => {
        const origin = request.headers.get("origin");
        const izinkan = process.env.ALLOWED_ORIGINS.split(",");
 
        if (!origin || !izinkan.includes(origin)) {
            throw error(403, "Origin tidak diizinkan");
        }
 
        const form = await request.formData();
        const email = String(form.get("email") ?? "");
 
        return { sukses: true };
    }
};

XSS and Automatic Escaping

Svelte escapes every value rendered through curly braces, so XSS via common expressions is prevented. The danger appears when you use {@html} to render raw HTML — that value must be sanitized first with a library like sanitize-html or DOMPurify before being rendered.

Rendering sanitized HTML
<script>
    import DOMPurify from "dompurify";
    import { browser } from "$app/environment";
 
    let { konten } = $props();
    let htmlAman = browser ? DOMPurify.sanitize(konten) : konten;
</script>
 
{@html htmlAman}

Input Sanitization and Secure Deployment

Parameterized Queries

User input that reaches SQL must go through a parameterized query, never concatenated directly into a string. This way the input's content is treated as data, not as part of the command, so SQL injection loses its medium.

JSSafe query with parameters
import db from "$lib/server/db";
 
export const load = async ({ url }) => {
    const nama = url.searchParams.get("nama") ?? "";
 
    const hasil = await db.query(
        "SELECT * FROM pengguna WHERE nama ILIKE $1",
        [`%${nama}%`]
    );
 
    return { hasil };
};

The same principle applies to other libraries: always use the built-in parameter mechanism of the ORM or database driver, and avoid building queries from strings that could contain user input.

Security Headers in Production

Complete your deployment with security headers: Content-Security-Policy to restrict script sources, Strict-Transport-Security to enforce HTTPS, and X-Content-Type-Options: nosniff. SvelteKit does not add these headers automatically, so configure them in your hosting platform or platform middleware.

Closing

Key takeaways:

  • API tokens are injected via the Authorization header from the server, not the client.
  • Never return secrets in load function values.
  • Secrets are read only in src/lib/server and validated when the application starts.
  • sameSite cookies plus origin verification fend off CSRF.
  • Svelte escapes output; sanitize before using {@html}.
  • Use parameterized queries and set security headers in production.

In the next episode we get into caching & performance: caching headers and revalidation, static generation, prerendering, streaming, edge caching and CDN integration, plus performance budgets and monitoring.