JWT JSON-WEB-TOKENS AUTHENTICATION AUTHORIZATION SECURITY WEB-SECURITY API BACKEND STATELESS TOKENS CRYPTOGRAPHY WEB-DEVELOPMENT BEST-PRACTICES

Memahami JSON Web Tokens (JWT): Fondasi Autentikasi Aplikasi Modern yang Aman

⏱️ 12 menit baca
👨‍💻

Memahami JSON Web Tokens (JWT): Fondasi Autentikasi Aplikasi Modern yang Aman

Selamat datang kembali di Sanggar Digital saya!

Dalam dunia pengembangan web modern, autentikasi dan otorisasi adalah dua pilar utama yang menentukan keamanan dan fungsionalitas aplikasi kita. Seiring dengan pergeseran arsitektur menuju API stateless dan microservices, metode autentikasi tradisional berbasis sesi (seperti session cookies) mulai terasa kurang fleksibel. Di sinilah JSON Web Tokens (JWT) hadir sebagai solusi elegan yang banyak digunakan.

Jika Anda pernah berinteraksi dengan API, membangun aplikasi single-page (SPA), atau mengembangkan mobile app, kemungkinan besar Anda sudah pernah mendengar atau bahkan menggunakan JWT. Namun, memahami JWT lebih dari sekadar menggunakannya. Implementasi JWT yang salah bisa membuka celah keamanan serius pada aplikasi Anda.

Artikel ini akan membawa Anda menyelami JWT: apa itu, bagaimana cara kerjanya, mengapa ia menjadi pilihan populer, dan yang terpenting, bagaimana mengimplementasikannya dengan aman. Mari kita mulai!

1. Pendahuluan: Mengapa Kita Butuh JWT?

Bayangkan Anda memiliki sebuah aplikasi web yang terdiri dari frontend (misalnya React, Vue, atau Angular) dan backend API terpisah. Setiap kali pengguna melakukan request ke API, backend perlu tahu siapa pengguna tersebut dan apakah mereka memiliki izin untuk melakukan tindakan yang diminta.

Secara tradisional, ini sering dilakukan dengan session berbasis cookie. Setelah login, server akan membuat sesi dan menyimpan ID sesi di cookie pengguna. Setiap request berikutnya akan membawa cookie ini, dan server akan mencocokkan ID sesi untuk mengidentifikasi pengguna.

Namun, pendekatan ini memiliki beberapa keterbatasan, terutama untuk arsitektur modern:

JWT mengatasi masalah ini dengan menyediakan cara yang stateless untuk bertukar informasi yang aman antara klien dan server. Alih-alih ID sesi, server memberikan “token” yang berisi informasi identitas pengguna dan data relevan lainnya, yang kemudian dikirimkan kembali oleh klien di setiap request.

💡 Intinya: JWT memungkinkan server untuk memverifikasi identitas pengguna dan otorisasi tanpa harus menyimpan status sesi di sisi server. Ini membuat aplikasi lebih mudah diskalakan dan fleksibel.

2. Apa Itu JWT? Struktur dan Cara Kerjanya

JSON Web Token (JWT) adalah standar terbuka (RFC 7519) yang mendefinisikan cara yang ringkas dan aman untuk mentransmisikan informasi antar pihak sebagai objek JSON. Informasi ini dapat diverifikasi dan dipercaya karena ditandatangani secara digital.

Sebuah JWT terdiri dari tiga bagian yang dipisahkan oleh titik (.), dan masing-masing bagian di-encode menggunakan Base64url:

HEADER.PAYLOAD.SIGNATURE

Mari kita bedah satu per satu:

2.1. Header (Kepala)

Header biasanya terdiri dari dua bagian: tipe token (yaitu JWT) dan algoritma hashing yang digunakan untuk signature (misalnya HMAC SHA256 atau RSA).

{
  "alg": "HS256",
  "typ": "JWT"
}

Setelah di-Base64url encode, akan terlihat seperti ini: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2.2. Payload (Muatan)

Payload berisi “klaim” (claims), yaitu pernyataan tentang entitas (biasanya pengguna) dan metadata tambahan. Ada tiga jenis klaim:

Contoh payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "exp": 1678886400 // March 15, 2023 12:00:00 AM GMT+07:00
}

Setelah di-Base64url encode, akan terlihat seperti ini: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTY3ODg4NjQwMH0

2.3. Signature (Tanda Tangan)

Bagian signature digunakan untuk memverifikasi bahwa token tidak diubah di tengah jalan, dan untuk memverifikasi bahwa token berasal dari pihak yang dipercaya.

Cara membuatnya:

  1. Ambil Base64url-encoded Header.
  2. Ambil Base64url-encoded Payload.
  3. Gabungkan keduanya dengan titik: encodedHeader + "." + encodedPayload.
  4. Hash string gabungan ini menggunakan algoritma yang ditentukan di Header (misalnya HS256) dan sebuah kunci rahasia (secret key) yang hanya diketahui oleh server.
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Contoh signature (setelah di-Base64url encode): TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT Lengkap: Menggabungkan ketiga bagian tersebut, Anda akan mendapatkan JWT yang terlihat seperti ini: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTY3ODg4NjQwMH0.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

📌 Penting: JWT bukan untuk menyembunyikan data (enkripsi)! JWT adalah tentang integritas data dan autentikasi pengirim. Siapa pun dapat membaca header dan payload dengan melakukan decode Base64url. Kunci rahasia hanya digunakan untuk membuat dan memverifikasi signature.

3. Alur Autentikasi dengan JWT

Mari kita lihat bagaimana JWT digunakan dalam alur autentikasi aplikasi web:

  1. Pengguna Login: Pengguna memasukkan kredensial (username/password) ke aplikasi klien (misalnya SPA).
  2. Klien Mengirim Kredensial: Aplikasi klien mengirimkan kredensial ke server backend melalui request HTTP (biasanya POST ke /api/login).
  3. Server Memverifikasi Kredensial: Server memverifikasi kredensial pengguna (misalnya dari database). Jika valid, server membuat JWT yang berisi informasi pengguna (misalnya ID pengguna, peran) di payload, menandatanganinya dengan kunci rahasia, dan mengirimkannya kembali ke klien.
  4. Klien Menyimpan JWT: Aplikasi klien menerima JWT dan menyimpannya (misalnya di local storage atau HttpOnly cookie).
  5. Klien Mengirim JWT di Setiap Request: Untuk setiap request berikutnya ke API yang memerlukan autentikasi, klien menyertakan JWT di header Authorization sebagai Bearer Token.
    Authorization: Bearer <your_jwt_token_here>
  6. Server Memverifikasi JWT: Ketika server menerima request dengan JWT, ia:
    • Memverifikasi signature JWT menggunakan kunci rahasia yang sama. Jika signature tidak valid, berarti token telah diubah atau tidak dikeluarkan oleh server ini.
    • Memverifikasi klaim-klaim penting seperti exp (kedaluwarsa), nbf (belum berlaku), iss (penerbit), dan aud (penerima).
    • Jika semua valid, server dapat mempercayai informasi di payload dan mengizinkan akses ke resource yang diminta.

Alur ini memungkinkan server untuk tetap stateless karena semua informasi yang dibutuhkan untuk autentikasi dan otorisasi sudah ada di dalam token itu sendiri.

4. Implementasi Aman JWT: Best Practices

Mengimplementasikan JWT dengan benar sangat krusial untuk keamanan aplikasi Anda. Berikut adalah beberapa praktik terbaik:

📌 4.1. Jangan Simpan JWT di Local Storage untuk Token Akses

Ini adalah kesalahan umum! Local storage rentan terhadap serangan Cross-Site Scripting (XSS). Jika penyerang berhasil menyuntikkan kode JavaScript berbahaya ke halaman Anda, mereka dapat dengan mudah mengakses dan mencuri JWT dari local storage.

Alternatif yang lebih aman:

💡 4.2. Gunakan Refresh Tokens untuk Token Akses Berumur Pendek

JWT (token akses) idealnya harus berumur pendek (misalnya 5-15 menit). Jika token akses dicuri, penyerang hanya memiliki jendela waktu yang singkat untuk menggunakannya.

Namun, meminta pengguna untuk login ulang setiap 5 menit jelas bukan pengalaman yang baik. Solusinya adalah menggunakan Refresh Tokens:

⚠️ 4.3. Validasi Semua Klaim Penting

Server Anda harus memvalidasi semua klaim penting setiap kali menerima JWT:

Banyak library JWT akan melakukan validasi ini secara otomatis, tapi Anda tetap perlu mengonfigurasinya dengan benar.

✅ 4.4. Selalu Gunakan HTTPS

Ini bukan hanya untuk JWT, tapi untuk keamanan web secara umum. JWT yang dikirim melalui HTTP (tanpa SSL/TLS) dapat dengan mudah di-intercept dan dicuri oleh penyerang. Selalu gunakan HTTPS untuk semua komunikasi antara klien dan server.

❌ 4.5. Hindari Payload Sensitif

Ingat, payload JWT hanya di-encode Base64url, bukan di-encrypt. Jangan pernah menyimpan informasi sensitif seperti password, nomor kartu kredit, atau PII (Personally Identifiable Information) yang tidak perlu di payload. Jika ada data sensitif yang perlu diakses, simpan di database dan gunakan ID referensi di JWT untuk mengambilnya.

🎯 4.6. Pilih Algoritma yang Kuat dan Rahasia yang Kuat

5. Contoh Konseptual Implementasi

Berikut adalah pseudocode sederhana untuk memberikan gambaran bagaimana JWT digunakan:

// ==== SISI SERVER (Node.js/Express dengan 'jsonwebtoken' library) ====

// 1. Endpoint Login
app.post("/api/login", async (req, res) => {
  const { username, password } = req.body;

  // 1a. Verifikasi kredensial pengguna (dari database)
  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return res.status(401).json({ message: "Kredensial tidak valid" });
  }

  // 1b. Buat Access Token (berumur pendek)
  const accessToken = jwt.sign(
    { userId: user._id, role: user.role },
    process.env.JWT_SECRET, // Kunci rahasia dari environment variable
    { expiresIn: "15m" }, // Token kedaluwarsa dalam 15 menit
  );

  // 1c. Buat Refresh Token (berumur panjang)
  const refreshToken = jwt.sign(
    { userId: user._id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: "7d" }, // Refresh token kedaluwarsa dalam 7 hari
  );

  // 1d. Simpan Refresh Token di database (untuk revocation)
  await RefreshToken.create({ userId: user._id, token: refreshToken });

  // 1e. Kirim Access Token dan Refresh Token ke klien
  //    Access Token bisa di body atau HttpOnly cookie.
  //    Refresh Token HARUS di HttpOnly cookie untuk keamanan.
  res.cookie("refreshToken", refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production", // Hanya di HTTPS di produksi
    sameSite: "Lax", // Perlindungan CSRF
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 hari
  });

  return res.json({ accessToken, message: "Login berhasil!" });
});

// 2. Middleware Autentikasi untuk endpoint yang dilindungi
const authenticateJWT = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (authHeader) {
    const token = authHeader.split(" ")[1]; // Ambil token dari "Bearer <token>"

    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
      if (err) {
        return res.sendStatus(403); // Token tidak valid atau kedaluwarsa
      }
      req.user = user; // Tambahkan data user ke request
      next();
    });
  } else {
    res.sendStatus(401); // Tidak ada token
  }
};

// 3. Endpoint yang dilindungi
app.get("/api/protected-data", authenticateJWT, (req, res) => {
  res.json({ message: `Halo, ${req.user.userId}! Ini data rahasia.` });
});

// 4. Endpoint Refresh Token
app.post("/api/refresh-token", async (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) {
    return res.sendStatus(401);
  }

  // 4a. Verifikasi Refresh Token
  const storedToken = await RefreshToken.findOne({ token: refreshToken });
  if (!storedToken) {
    return res.sendStatus(403); // Refresh token tidak ada di database (mungkin sudah dibatalkan)
  }

  jwt.verify(
    refreshToken,
    process.env.REFRESH_TOKEN_SECRET,
    async (err, user) => {
      if (err) {
        // Jika refresh token tidak valid, hapus dari DB dan cookie
        await RefreshToken.deleteOne({ token: refreshToken });
        res.clearCookie("refreshToken");
        return res.sendStatus(403);
      }

      // 4b. Buat Access Token baru
      const newAccessToken = jwt.sign(
        { userId: user.userId, role: user.role }, // Ambil peran dari user di database
        process.env.JWT_SECRET,
        { expiresIn: "15m" },
      );

      res.json({ accessToken: newAccessToken });
    },
  );
});

// ==== SISI KLIEN (React/Vue/Angular) ====

// Setelah login berhasil, simpan accessToken
// const { accessToken } = response.data;
// localStorage.setItem('accessToken', accessToken); // Contoh, tapi HttpOnly cookie lebih baik

// Untuk setiap request yang memerlukan autentikasi
// axios.get('/api/protected-data', {
//   headers: {
//     Authorization: `Bearer ${localStorage.getItem('accessToken')}`
//   }
// });

Kesimpulan

JSON Web Tokens adalah alat yang sangat ampuh dan fleksibel untuk autentikasi dan otorisasi di aplikasi modern. Dengan sifatnya yang stateless, JWT sangat cocok untuk API, microservices, dan aplikasi single-page yang skalabel.

Namun, kekuatan ini datang dengan tanggung jawab. Memahami struktur JWT, alur autentikasi, dan yang terpenting, menerapkan praktik keamanan terbaik adalah kunci untuk membangun aplikasi yang robust dan terhindar dari kerentanan. Selalu ingat: JWT itu ditandatangani, bukan dienkripsi. Dan selalu prioritaskan keamanan dalam penyimpanan serta validasi token Anda.

Semoga artikel ini memberikan pemahaman yang lebih dalam tentang JWT dan membantu Anda membangun aplikasi web yang lebih aman dan efisien!

🔗 Baca Juga