Membangun autentikasi yang aman di frontend: perbedaan autentikasi dan otorisasi, session vs JWT, alur OAuth & SSO, dan penyimpanan token yang benar (HttpOnly cookie) — dipraktikkan pada alur login TokoKita

TokoKita kini punya checkout — dan checkout butuh tahu siapa pengguna yang bertransaksi. Episode 19 membahas authentication & authorization: dua konsep yang sering dicampuradukkan, dengan keputusan arsitektur yang berdampak pada keamanan jangka panjang. Di sini kalian juga akan melihat bagaimana frontend dan backend benar-benar bekerja sama.
Mengapa topik ini wajib? Karena "login" adalah gerbang produk: kesalahan di sini berarti akun diretas, data bocor, atau pengguna terkunci. Dan karena model autentikasi (session vs JWT) menentukan hampir semua keputusan berikutnya — dari storage sampai middleware.
| Autentikasi (Authn) | Otorisasi (Authz) | |
|---|---|---|
| Pertanyaan | "Siapa kamu?" | "Bolehkah kamu melakukan ini?" |
| Bukti | Kredensial / token | Role / permission |
| Contoh | Login dengan password | Hanya admin yang bisa hapus produk |
| Lapisan | Sesi & token | Policy & role |
Autentikasi dulu, baru otorisasi. Di frontend, kalian menyimpan bukti autentikasi dan menampilkan/menyembunyikan UI berdasarkan otorisasi — tetapi keputusan otorisasi final selalu di server (UI hanya kosmetik).
Dua pendekatan utama menyimpan "siapa kamu":
Session — server menyimpan data sesi, klien hanya memegang sessionId (cookie).
Login → server buat session + set cookie sessionId
Setiap request → browser kirim cookie → server cek DB/memoriJWT (JSON Web Token) — klien memegang token berisi data yang ditandatangani server.
header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.s3cr3t...
header → algoritma & tipe
payload → klaim (userId, role, exp)
signature → bukti tidak diubah (ditandatangani server)| Session | JWT | |
|---|---|---|
| Stateless server | Tidak (perlu store) | Ya |
| Revoke (logout/password berubah) | Mudah | Sulit (sampai exp) |
| Skala banyak server | Perlu shared store | Mudah |
| Penggunaan umum | Web app klasik | API & SPA/edge |
Note
Keputusan session vs JWT adalah keputusan tim, bukan kebenaran mutlak. Yang bukan pilihan: menyimpan JWT di localStorage tanpa alasan kuat — kalian akan lihat kenapa di bagian secure storage.
OAuth 2.0 memungkinkan login dengan identitas pihak ketiga (Google, GitHub) tanpa membocorkan password. SSO (Single Sign-On) memungkinkan satu login untuk banyak aplikasi dalam satu organisasi.
1. User klik "Login dengan Google"
2. Redirect ke Google → user setuju → Google kirim code ke server kita
3. Server tukar code → access token (tanpa libatkan browser dengan token)
4. Server buat sesi untuk aplikasi → cookie HttpOnly
5. Aplikasi dapat dipakaiPola kuncinya: token pihak ketiga ditukar di server, bukan di browser. Detail implementasi di Next.js (mis. NextAuth/Auth.js) akan kita rakit di praktik.
Ini bagian yang paling sering salah di proyek frontend:
| Lokasi | Aman untuk token? | Masalah |
|---|---|---|
localStorage | ❌ | Dibaca semua JavaScript — satu XSS (episode 18) = token dicuri |
sessionStorage | ❌ | Sama rentannya terhadap XSS |
Cookie HttpOnly | ✅ | JS tidak bisa baca; dikirim otomatis; + Secure + SameSite |
Aturan emas: token autentikasi di cookie HttpOnly, karena ia tidak bisa dijangkau script penyerang. Jangan simpan token di localStorage — XSS kecil langsung menjadi pembajakan akun.
Arsitektur yang aman di Next.js App Router:
app/
├── login/page.tsx # form login (client)
├── api/auth/login/route.ts # cek kredensial → set cookie HttpOnly
└── middleware.ts # proteksi halamanimport { NextResponse } from "next/server";
import { signToken } from "@/lib/auth";
export async function POST(req: Request) {
const { email, password } = await req.json();
const user = await verifyCredentials(email, password);
if (!user) return NextResponse.json({ error: "Kredensial salah" }, { status: 401 });
const token = signToken({ userId: user.id, role: user.role });
const res = NextResponse.json({ ok: true });
res.cookies.set("session", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24,
});
return res;
}import { NextRequest, NextResponse } from "next/server";
export function middleware(req: NextRequest) {
const session = req.cookies.get("session")?.value;
if (!session) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/checkout/:path*", "/account/:path*"] };Validasi input dari form (email/password) dengan skema (mis. Zod) sebelum diproses — jangan percaya input mentah (episode 18).
| Kesalahan | Dampak | Perbaikan |
|---|---|---|
Token di localStorage | XSS = akun dicuri | Cookie HttpOnly |
| JWT umur panjang tanpa revoke | Logout tidak berfungsi | Sesi pendek + refresh |
| Otorisasi hanya di UI | API bisa dipanggil langsung | Cek role di server |
| Password disimpan/dikirim polos | Bocor | Hash + HTTPS saja |
SameSite=None tanpa alasan | Serangan CSRF lebih mudah | SameSite=Lax default |
Inti yang harus dibawa pulang:
HttpOnly + Secure + SameSite, bukan localStorage.Di episode 20 selanjutnya kita mendekatkan aplikasi ke pengguna: edge computing & rendering — CDN, edge functions, dan strategi rendering SSG/SSR/ISR untuk TokoKita yang cepat di seluruh dunia. Sampai jumpa di episode 20!