Episode ini membahas keamanan pada pengambilan data: request API yang aman dengan auth headers, handling secrets dan server-only config, perlindungan CSRF dan XSS, input sanitization, serta konfigurasi deployment yang aman.

Setelah mempelajari authentication di episode 12, episode 13 berfokus pada keamanan pengambilan dan pengiriman data. Ini adalah lapisan pertahanan yang paling sering diabaikan: request API yang bocor token, secret yang tertanam di bundle client, hingga input yang tidak dibersihkan.
SvelteKit memberikan batas yang jelas antara server dan client, tetapi batas itu hanya berguna jika dimanfaatkan dengan benar. Semua data sensitif harus tetap berada di sisi server, dan setiap jalur masuk data dari pengguna harus diverifikasi.
Setelah episode ini, kalian bisa menulis aplikasi yang memegang secret dengan benar, menolak request yang mencurigakan, dan membersihkan input sebelum disimpan atau dirender.
Panggilan API eksternal yang butuh token dilakukan dari load function server, bukan dari browser. Token dibaca dari environment variable dan disisipkan lewat header Authorization, tanpa pernah terlihat oleh JavaScript di client.
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() };
};Load function server tidak pernah dikirim ke browser; hanya nilai yang dikembalikan yang di-serialisasi. Karena itu token aman selama kalian tidak menyimpan secret di dalam nilai yang dikembalikan.
Periksa setiap nilai yang dikembalikan load function. Jangan mengembalikan objek user yang berisi passwordHash, apiKey, atau token mentah. Pilih hanya field yang dibutuhkan UI, atau bangun objek baru secara eksplisit agar struktur yang dipakai UI tidak menyeret data internal.
export const load = async ({ locals }) => {
const user = locals.user;
return {
profil: {
id: user.id,
nama: user.nama,
avatar: user.avatar
}
};
};Di SvelteKit, variabel yang diawali PUBLIC_ bisa diakses dari client lewat import.meta.env.PUBLIC_NAMA. Semua secret harus disimpan di environment variable tanpa prefix dan hanya dibaca di server, misalnya melalui modul di src/lib/server.
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");
}File di src/lib/server hanya bisa diimpor dari kode server. Impor dari komponen client akan gagal di build, sehingga secret yang bersembunyi di sana tidak bisa bocor tanpa sengaja.
Validasi environment di awal startup mencegah aplikasi berjalan dengan konfigurasi yang salah. Skema validasi bisa ditulis manual atau memakai pustaka seperti Zod. Dengan npm install zod, kalian bisa memeriksa kehadiran dan format tiap variabel sekali, lalu memakai hasilnya di seluruh server.
Cookie sameSite mencegah sebagian besar serangan CSRF, tetapi untuk request yang dikirim lintas origin, verifikasi header Origin menambah lapisan pertahanan. Server action yang sensitif sebaiknya memastikan origin request cocok dengan origin aplikasi.
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 };
}
};Svelte men-escape semua nilai yang dirender lewat kurung kurawal, sehingga XSS lewat ekspresi umum bisa dicegah. Bahayanya muncul ketika kalian memakai {@html} untuk merender HTML mentah — nilai itu harus disanitasi dulu dengan pustaka seperti sanitize-html atau DOMPurify sebelum dirender.
<script>
import DOMPurify from "dompurify";
import { browser } from "$app/environment";
let { konten } = $props();
let htmlAman = browser ? DOMPurify.sanitize(konten) : konten;
</script>
{@html htmlAman}Input dari user yang masuk ke SQL harus melewati parameterized query, bukan digabungkan langsung ke string. Dengan cara ini, isi input diperlakukan sebagai data, bukan bagian dari perintah, sehingga injeksi SQL kehilangan medianya.
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 };
};Prinsip yang sama berlaku untuk library lain: selalu pakai mekanisme parameter bawaan ORM atau driver database, dan hindari membangun query dari string yang bisa mengandung input pengguna.
Lengkapi deployment dengan security headers: Content-Security-Policy untuk membatasi sumber skrip, Strict-Transport-Security untuk memaksa HTTPS, serta X-Content-Type-Options: nosniff. SvelteKit tidak menambahkan header ini secara otomatis, jadi pasang di platform hosting atau middleware platform kalian.
Inti yang harus dibawa pulang:
Authorization dari server, bukan dari client.src/lib/server dan divalidasi saat aplikasi start.sameSite plus verifikasi origin melawan CSRF.{@html}.Di episode 14 selanjutnya kita membahas caching & performance: caching headers dan revalidation, static generation, prerendering, streaming, edge caching dan integrasi CDN, serta performance budgets dan monitoring.