Apa Itu JWT Json Web Token

pindipin
20 October 2026
15 min read

Kamu baru selesai membangun REST API untuk aplikasi mobile klien. Karena kebanyakan tutorial menyebutkan JWT adalah standar autentikasi API modern, kamu memilih JWT tanpa ragu. Beberapa minggu kemudian, klien meminta fitur "logout paksa semua perangkat" setelah pengguna dicurangi. Kamu mencoba mencabut token yang diterbitkan, tetapi menemukan bahwa JWT murni tidak bisa dicabut seketika seperti session. Token tetap berlaku sampai masa waktu kadaluwarsa habis. Itu adalah momen banyak developer menyadari bahwa JWT bukan hanya sekadar "ganti session yang lebih modern": ada trade-off yang perlu dipahami sebelum memutuskan menggunakannya.

Apa Itu JWT (JSON Web Token)?

JWT singkatan dari JSON Web Token. Menurut RFC 7519, JWT adalah representasi kompak dan aman URL dari sekumpulan klaim (claims) yang dipindahkan antara dua pihak. Dalam konteks autentikasi, klaim biasanya berisi informasi tentang pengguna, misalnya ID pengguna, nama, dan status admin.

Yang membuat JWT berbeda dari token biasa adalah struktur sendiri-caranya. Semua informasi yang diperlukan untuk mengidentifikasi pengguna sudah terkandung di dalam token itu sendiri. Server yang menerbitkan token tidak perlu menyimpan data sesi di database, dan server yang menerima token hanya perlu memverifikasi tanda tangan untuk yakin bahwa token asli dan belum diubah.

JWT populer untuk autentikasi API karena pendekatan ini bersifat stateless. Server tidak perlu mencari sesi pengguna di database pada setiap request. Hal ini berguna saat kamu membangun API untuk aplikasi mobile, mikroservices, atau arsitektur serverless di mana setiap layanan perlu memverifikasi pengguna secara independen tanpa bergantung pada penyimpanan sesi terpusat.

Format JWT dalam bentuk compact serialization terdiri dari tiga bagian yang dipisahkan dengan titik (.): header.payload.signature. Setiap bagian di-encode menggunakan Base64URL. Berikut adalah contoh JWT utuh:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Bagian pertama adalah header, bagian kedua adalah payload, dan bagian ketiga adalah signature. Kamu dapat mendekode header dan payload untuk melihat isinya, tetapi signature tidak dapat dipalsukan tanpa kunci rahasia server.

Perbedaan JWT dengan Token Opaque

Sebelum melanjutkan, penting untuk memahami perbedaan antara JWT dengan token opaque. Token opaque adalah string acak yang tidak berarti apa bagi siapa pun yang melihatnya. Validitasnya hanya dapat diperiksa oleh server penerbit melalui database atau introspection endpoint.

JWT sebaliknya berisi klaim yang dapat dibaca dan diverifikasi langsung dari token itu sendiri. Jika kamu memiliki kunci verifikasi, kamu tidak perlu query ke database untuk tahu pemilik token.

Struktur JWT Secara Mendalam (Header, Payload, Signature)

Setiap bagian JWT memiliki peran yang berbeda. Memahami struktur ini penting agar kamu tidak salah dalam implementasi dan mengamankan token dengan benar.

Bagian Pertama: Header (JOSE Header)

Header berisi metadata tentang jenis token dan algoritma kriptografi untuk mengamankan isi token. Contoh header dalam bentuk JSON:

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

Parameter alg menunjukkan algoritma signing yang digunakan, misalnya HS256, RS256, atau ES256.

Ada juga parameter tambahan seperti kid (Key ID) yang berguna untuk mengidentifikasi kunci tertentu saat melakukan rotasi kunci. Dengan kid, server tahu kunci mana yang harus digunakan untuk memverifikasi token tanpa harus mencoba semua kunci yang tersedia.

Bagian Kedua: Payload (JWT Claims Set)

Payload berisi sekumpulan klaim tentang subjek (biasanya pengguna) dan atribut tambahan. Klaim dibagi menjadi tiga tipe menurut IANA JWT Claims Registry:

Klaim terdaftar (registered claims) adalah klaim standar yang disarankan untuk interoperabilitas:

  • iss (issuer): penerbit token
  • sub (subject): subjek token, biasanya ID pengguna
  • aud (audience): penerima yang dimaksudkan token
  • exp (expiration time): waktu kedaluwarsa token
  • nbf (not before): waktu sebelum token tidak valid
  • iat (issued at): waktu token diterbitkan
  • jti (JWT ID): pengidentifikasi unik token, berguna untuk revokasi

Contoh payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516242622
}

Selain klaim terdaftar, ada klaim publik dan klaim private/custom yang disepakati antara penerbit dan penerima. Kamu dapat menambahkan klaim custom sesuai kebutuhan, misalnya role atau izin pengguna.

Bagian Ketiga: Signature (Tanda Tangan)

Signature dihasilkan dengan mengambil Base64URL(header) dan Base64URL(payload), menggabungkannya dengan titik, lalu ditandatangani menggunakan algoritma dari header:

HMACSHA256(
  base64urlEncode(header) + "." + base64urlEncode(payload),
  secret_key
)

Signature menjamin integritas dan keaslian token. Setiap perubahan pada header atau payload, misalnya mengubah admin: false menjadi admin: true, akan menghasilkan signature yang tidak valid. Server yang menerima token akan menghitung ulang signature dan membandingkannya dengan signature yang disertakan dalam token. Jika tidak cocok, token ditolak. Klaim dalam token juga terkait dengan otorisasi, karena dapat menentukan apa yang boleh dilakukan pengguna.

Contoh Lengkap JWT

Mari kita pecah contoh JWT di atas menjadi tiga bagian:

Bagian 1 (header):

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

Bagian 2 (payload):

{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}

Bagian 3 (signature):
Tanda tangan kriptografis yang dihasilkan dari header dan payload dengan kunci rahasia server.

Pentingnya: Payload Tidak Dienkripsi

Payload JWT dalam JWS standar hanya ditandatangani, tidak dienkripsi. Siapa pun yang memiliki token dapat mendekode payload dan melihat isinya. Karena itu, jangan pernah menyimpan data sensitif seperti password, hash password, nomor KTP, atau nomor kartu kredit di payload JWT. Data di payload harus dianggap publik bagi siapa pun yang menangkap token.

Implementasi JWT dengan PHP

Di sisi PHP, kamu dapat menggunakan library seperti firebase/php-jwt atau lcobucci/jwt untuk membuat dan memverifikasi JWT.

Contoh kode di bawah ini ditargetkan untuk firebase/php-jwt v6.x (kelas Key diperlukan mulai v5.x), lcobucci/jwt 4.x, dan PHP 8.2+. Sesuaikan versi library dengan composer.json kamu jika menggunakan rilis yang berbeda.

Contoh dengan firebase/php-jwt:

<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$key = 'your_secret_key';
$payload = [
    'iss' => 'https://contoh-api.com',
    'sub' => '123',
    'iat' => time(),
    'exp' => time() + 3600,
];

$jwt = JWT::encode($payload, $key, 'HS256');
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));

Contoh dengan lcobucci/jwt:

<?php
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

$config = Configuration::forSymmetricSigner(
    new Sha256(),
    InMemory::plainText('your_secret_key')
);

$token = $config->builder()
    ->issuedBy('https://contoh-api.com')
    ->permittedFor('https://contoh-api.com')
    ->identifiedBy('jti-unique-id')
    ->issuedAt(new \DateTimeImmutable())
    ->expiresAt(new \DateTimeImmutable('+1 hour'))
    ->withClaim('sub', '123')
    ->getToken($config->signer(), $config->signingKey());

$string = $token->toString();

Kedua library memerlukan verifikasi signature pada setiap request. Jangan hanya mendekode token tanpa memverifikasi signature, karena itu membiarkan token palsu diterima.


Cara Kerja JWT (Alur Autentikasi Stateless)

Alur autentikasi typikal menggunakan JWT:

  1. Client mengirim kredensial login (email dan password) ke server.

  2. Server memverifikasi kredensial terhadap data pengguna di database.

  3. Jika valid, server membuat JWT berisi klaim pengguna (misalnya sub = ID pengguna, iat, dan exp), lalu menandatanginya dengan kunci rahasia.

  4. Server mengirim JWT ke client sebagai respons login.

  5. Client menyimpan token, misalnya di HttpOnly cookie atau localStorage.

  6. Pada setiap request berikutnya, client menyertakan JWT di header Authorization: Bearer <token>.

  7. Server memverifikasi signature dan klaim standar (exp, nbf, iss, aud). Jika valid, server mengekstrak identitas pengguna dari klaim tanpa query ke database.

  8. Server memberikan akses ke resource yang diminta.

Server biasanya tidak perlu query ke database sesi untuk memvalidasi JWT. Informasi identitas pengguna sudah ada di dalam token.


JWT vs Session-Based Authentication (Perbandingan & Trade-off)

Banyak developer berpikir JWT adalah versi modern dan lebih baik dari session-based authentication. Keduanya memiliki kelebihan dan kekurangan masing-masing. Memilih yang salah dapat menyebabkan masalah keamanan atau skalabilitas di kemudian hari.

Session-Based Authentication (Stateful)

Dalam pendekatan stateful, server menyimpan data sesi pada penyimpanan sisi server, misalnya file, database, atau Redis. Setelah login, server menghasilkan session ID dan mengirimkannya ke client melalui cookie. Pada setiap request, browser mengirimkan session ID otomatis melalui cookie, dan server mencocokkannya dengan data sesi yang tersimpan.

Pendekatan ini cocok untuk aplikasi monolith tradisional dengan Blade, Livewire, atau Inertia.js. Server memiliki kontrol penuh atas sesi dan dapat dengan mudah mencabut sesi kapan saja, misalnya saat pengguna menekan tombol logout atau admin melarang pengguna.

Kelemahan session-based authentication adalah skalabilitas horizontal. Jika kamu memiliki beberapa server, semua server perlu mengakses penyimpanan sesi yang sama (misalnya Redis terpusat) agar sesi pengguna dapat diakses dari mana saja.

JWT-Based Authentication (Stateless)

JWT tidak menyimpan state di server. Semua informasi identitas pengguna terkandung di dalam token yang disimpan di sisi client. Server hanya memverifikasi signature dan klaim token pada setiap request. Ini membuat JWT sangat skalabel karena setiap layanan dapat memverifikasi token secara independen tanpa query ke pusat otentikasi.

Kelemahan utama JWT adalah revokasi. Token yang sudah diterbitkan berlaku sampai masa exp habis. Jika kamu ingin mencabut token segera, misalnya saat password diganti atau pengguna dicurangi, kamu perlu mekanisme tambahan seperti blacklist token, yang membatalkan keuntungan stateless.

Membandingkan Keduanya dalam Praktik

Penyimpanan state adalah perbedaan paling mendasar. Session-based menyimpan data sesi di server, sementara JWT menyimpan state di sisi client melalui token. Session-based membutuhkan lookup database atau cache untuk memvalidasi setiap request, sedangkan JWT hanya memerlukan verifikasi kriptografis.

Dalam hal skalabilitas horizontal, JWT lebih unggul karena tidak ada ketergantungan pada penyimpanan sesi terpusat. Setiap layanan dalam mikroservices dapat memverifikasi JWT menggunakan kunci yang sama tanpa bertanya ke layanan autentikasi pusat.

Ukuran header juga berbeda: session ID cuma berupa string pendek (sekitar 32-40 karakter), sedangkan JWT lebih besar (300-1000 byte atau lebih) karena berisi semua klaim.

Revokasi lebih mudah dalam session-based: kamu dapat menghapus sesi dari server secara instan. JWT sulit dicabut instan tanpa state tambahan di server.

Bearer token di header Authorization tidak rentan CSRF karena browser tidak menyertakannya pada request lintas situs. Namun, JWT di cookie tetap rentan CSRF. Cookie HttpOnly melindungi dari pencurian via JavaScript, sedangkan localStorage rentan jika ada kerentanan XSS.

Session-based cocok untuk web monolith dan first-party SPA pada domain yang sama. JWT lebih sesuai untuk API mobile, mikroservices, public API third-party, dan serverless. Untuk memahami perbedaan antara authentication dan authorization secara lebih detail, kamu dapat membaca panduan perbedaan keduanya.


Keamanan JWT: Ancaman, Kerentanan, dan Mitigasi

JWT adalah teknologi yang powerful, tetapi juga rentan jika diimplementasikan salah. Berikut adalah ancaman umum dan cara mengataminya.

Verifikasi Signature Wajib pada Setiap Request

Server harus memverifikasi signature pada setiap request. Beberapa library memiliki fungsi yang hanya mendekode token tanpa memverifikasi signature, misalnya jwt.decode. Jika kamu menggunakan fungsi decode tanpa verifikasi, token palsu dapat diterima oleh server. Selalu validasi klaim standar juga: exp harus masih di masa depan, nbf harus masih di masa lalu, iss harus cocok dengan penerbit tepercaya, dan aud harus cocok dengan identifier layanan yang diharapkan.

Algoritma none (Critical)

JWT mendefinisikan algoritma signing bernama none, yang berarti tidak ada tanda tangan sama sekali. Beberapa library lama menerima token dengan alg: none sebagai token yang valid, sehingga penyerang dapat membuat token palsu dengan payload apa pun dan server akan menerima. Kerentanan ini dikenal sebagai CVE-2015-9235.

Mitigasinya adalah menonaktifkan algoritma none pada JWT parser dan verifier. Hanya izinkan algoritma yang dipercaya, misalnya HS256, RS256, atau ES256. Jangan biarkan aplikasi menerima algoritma dari header token oleh penyerang.

Algorithm Confusion (HS256 vs RS256)

Jika server memverifikasi RS256 menggunakan kunci publik RSA, penyerang dapat mengubah alg menjadi HS256 dan menandatanginya menggunakan kunci publik RSA sebagai kunci rahasia HMAC. Server akan memverifikasi dengan HMAC menggunakan kunci publik, sehingga token palsu diterima.

Mitigasinya adalah menerapkan kunci spesifik per algoritma. Satu kunci untuk satu algoritma. Gunakan kid untuk lookup kunci dan algoritma, dan jangan terima algoritma dari header tanpa validasi yang ketat.

Penyimpanan Token di Sisi Client

Lokasi penyimpanan token memengaruhi risiko keamanan. localStorage atau sessionStorage rentan terhadap pencurian melalui XSS karena script jahat dapat membaca token. Tidak disarankan untuk menyimpan token sensitif di sini. HttpOnly cookie tidak dapat diakses JavaScript, melindungi dari XSS, tetapi tetap rentan CSRF jika tidak dikombinasikan dengan SameSite dan CSRF token. Rekomendasi adalah menyimpan JWT di cookie HttpOnly; Secure; SameSite=Lax/Strict, atau menggunakan pendekatan short-lived access token dengan rotating refresh token.

XSS dan CSRF

JWT di localStorage dapat dicuri melalui injeksi skrip XSS, memberikan akses penuh kepada penyerang sampai token kedaluwarsa. Mitigasi meliputi sanitasi input dan output, Content Security Policy (CSP), dan penggunaan HttpOnly cookie.

Bearer token di header Authorization: Bearer <token> tidak rentan CSRF karena browser tidak menyertakan header tersebut secara otomatis pada request lintas situs. Namun, JWT di cookie tetap rentan CSRF. Mitigasi dengan SameSite cookie dan CSRF token.

Revokasi dan Invalidation Token

JWT murni adalah stateless dan tidak dapat di-revoke secara instan tanpa menyimpan state di server. Token yang telah diterbitkan berlaku sampai masa exp habis. Untuk logout instan atau mencabut token, misalnya password diganti atau pengguna dicurangi, kamu perlu mekanisme tambahan:

  • Token blacklist / revocation list: Simpan jti (JWT ID) token yang dicabut di Redis atau database dengan TTL sama dengan waktu kedaluwarsa token. Server memeriksa blacklist pada setiap request, meski ini setengahnya membatalkan keuntungan stateless.
  • Short-lived access token + refresh token: Access token berlaku singkat, misalnya 15 menit. Refresh token berlaku lebih lama dan digunakan untuk memperoleh access token baru tanpa login ulang.
  • Refresh token rotation: Setiap penggunaan refresh_token menghasilkan access_token dan refresh_token baru, dan refresh_token lama dicabut. Hal ini membantu mendeteksi pencurian refresh token.

Refresh token adalah token panjang masa untuk memperoleh access token baru tanpa login ulang. Best practice: access token short-lived, refresh token long-lived, simpan di HttpOnly cookie, terapkan rotasi, batasi jumlah aktif per pengguna, dan cabut saat logout atau mencurigai pencurian.


JWT di Ekosistem Laravel (Sanctum, Passport, tymon/jwt-auth)

Laravel menawarkan beberapa opsi autentikasi, termasuk JWT. Memilih opsi yang tepat tergantung pada kebutuhan aplikasi kamu.

Laravel Session (Web Guard)

Session guard adalah bawaan Laravel untuk aplikasi monolith dengan Blade, Livewire, atau Inertia.js. Menggunakan cookie session dengan CSRF protection. Pendekatan ini stateful dan cocok untuk aplikasi web tradisional.

Laravel Sanctum

Sanctum digunakan untuk SPA, aplikasi mobile, dan simple token-based API. Ada dua mode:

Mode SPA: Menggunakan cookie session Laravel bawaan dengan CSRF protection. Cocok untuk first-party SPA pada domain yang sama dengan backend Laravel. Melindungi dari XSS melalui HttpOnly cookie dan sudah dilindungi CSRF.

Mode API Token: Membuat personal access token yang disimpan (hash) di database personal_access_tokens, dikirim via Authorization: Bearer <token>. Secara teknis ini semi-stateful karena server perlu lookup database untuk memvalidasi token, bukan JWT murni stateless.

Contoh pembuatan token Sanctum:

<?php
use Laravel\Sanctum\PersonalAccessToken;

$token = $user->createToken('api-token', ['server:read']);
$plainTextToken = $token->plainTextToken;

Laravel Passport

Passport adalah implementasi OAuth 2.0 server lengkap dengan berbagai grant types dan scopes. Digunakan untuk authorization server penuh, misalnya login dengan pihak ketiga atau komunikasi server-to-server. Passport menggunakan JWT untuk access token melalui League OAuth2 server.

Contoh pengaturan Passport di AuthServiceProvider:

<?php
use Laravel\Passport\Passport;

public function boot(): void
{
    Passport::routes();
}

tymon/jwt-auth (Paket Komunitas)

tymon/jwt-auth adalah paket komunitas populer untuk JWT authentication di Laravel dan Lumen. Menyediakan jwt guard, middleware auth:jwt, dan fitur login, logout, serta refresh. Cocok untuk pure stateless JWT API, tetapi memerlukan perhatian ekstra pada verifikasi signature dan algoritma none.

Contoh konfigurasi guard di config/auth.php:

<?php
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

Contoh login dan pembuatan token di controller:

<?php
use Tymon\JWTAuth\Facades\JWTAuth;

$credentials = request(['email', 'password']);

if (!$token = JWTAuth::attempt($credentials)) {
    return response()->json(['error' => 'Invalid credentials'], 401);
}

return response()->json(['access_token' => $token]);

Contoh middleware untuk melindungi route:

<?php
Route::middleware('auth:jwt')->get('/user', function () {
    return auth()->user();
});

Pastikan juga untuk menonaktifkan algoritma none dan verifikasi signature dengan benar saat menggunakan paket ini.

Perbandingan Opsi Laravel

Laravel Session cocok untuk monolith tradisional yang membutuhkan instant revocation. Sanctum mode SPA cocok untuk first-party SPA pada domain yang sama. Sanctum mode API Token cocok untuk simple API token tanpa OAuth lengkap. Passport cocok untuk OAuth 2.0 server penuh. tymon/jwt-auth cocok untuk pure stateless JWT API.


Kapan Menggunakan JWT vs Kapan Tidak

Gunakan JWT jika:

  • Kamu membangun RESTful API untuk aplikasi mobile native (Android atau iOS) yang tidak mengelola cookie dan CSRF seperti browser.
  • Kamu menggunakan arsitektur mikroservices di mana layanan lain perlu memverifikasi pengguna tanpa query terpusat ke layanan autentikasi.
  • Kamu membangun public API dengan integrasi server-to-server third-party.
  • Kamu menggunakan serverless atau FaaS di mana tidak ada session store terpusat.

Jangan gunakan JWT (pilih session) jika:

  • Kamu membangun aplikasi web monolith tradisional dengan Blade, Livewire, atau Inertia.js.
  • Kamu membutuhkan fitur instant revocation, misalnya tombol "logout dari semua perangkat" atau melarang pengguna segera.
  • Kamu membangun first-party SPA pada domain yang sama dengan backend Laravel. Gunakan Laravel Sanctum dengan cookie session, yang lebih aman dari XSS dan sudah dilindungi CSRF.
  • Kamu tidak memiliki kebutuhan skalabilitas yang memaksa stateless authentication.

Rule of thumb developer Laravel: web monolith atau first-party SPA, gunakan session atau Sanctum cookie. API untuk mobile atau mikroservices, pertimbangkan JWT.


Best Practices & Common Pitfalls

Best Practices

Selalu verifikasi signature pada setiap request dan validasi klaim standar seperti exp, nbf, iss, dan aud. Nonaktifkan algoritma none dan gunakan algoritma kuat seperti HS256, RS256, atau ES256. Simpan token di cookie HttpOnly; Secure; SameSite, atau gunakan pendekatan short-lived access token dengan rotating refresh token. Jangan simpan data sensitif di payload JWT.

Common Pitfalls

Menyimpan JWT di localStorage membuat token rentan dicuri melalui XSS. Mengabaikan verifikasi signature atau mengizinkan algoritma none membiarkan token palsu diterima server. Memasukkan password atau data sensitif di payload JWT membuka risiko kebocoran data. Access token berumur panjang tanpa refresh token membuat revokasi sulit. Mengandalkan JWT untuk logout instan tanpa blacklist akan gagal saat dibutuhkan.


Penutup

JWT adalah representasi kompak dan aman URL dari sekumpulan klaim yang dipindahkan antara dua pihak. Dengan struktur header.payload.signature, JWT memungkinkan autentikasi stateless di mana server tidak perlu menyimpan data sesi untuk memverifikasi pengguna. Kelebihan utamanya adalah skalabilitas yang tinggi, cocok untuk API mobile, mikroservices, dan serverless. Namun, JWT juga memiliki keterbatasan, terutama dalam hal revokasi token yang sulit dilakukan secara instan.

Keputusan apakah harus menggunakan JWT tergantung pada arsitektur aplikasi kamu. Untuk web monolith dan first-party SPA di Laravel, session atau Sanctum cookie session biasanya pilihan yang lebih aman dan lebih mudah dikelola. Untuk API mobile dan mikroservices, JWT dapat menjadi solusi yang tepat asalkan kamu memahami risiko keamanannya.

Untuk mempelajari implementasi dan konsep dasar JWT lebih lanjut, kamu dapat membaca dokumentasi resmi RFC 7519 tentang JSON Web Token dan OWASP JSON Web Token Cheat Sheet.

Jika kamu ingin mendalami topik keamanan dan autentikasi terkait, baca artikel tentang Authentication dan Cara Kerjanya, Authorization dan Cara Kerjanya, dan Perbedaan Authentication dan Authorization.

Bagikan Artikel:
Diskusi & Komentar

Fitur komentar belum diaktifkan oleh administrator.

Artikel Terkait