Apa itu JWT?
JWT (JSON Web Token) itu standar terbuka (RFC 7519) buat ngirim informasi antar pihak secara aman dalam bentuk JSON. JWT paling sering dipakai buat authentication dan authorization.
Bentuknya kalau dilihat kayak string random panjang yang dipisah titik:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikx1dGhmaSIsInJvbGUiOiJ1c2VyIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Kelihatan kayak encrypted? Sebenernya engga. JWT itu cuma di-encode, bukan di-encrypt. Bedanya jauh.
Struktur JWT: Header, Payload, Signature
JWT terdiri dari tiga bagian, dipisah titik (.):
HEADER.PAYLOAD.SIGNATURE
1. Header
Header berisi info tentang tipe token dan algoritma yang dipakai buat bikin signature.
{
"alg": "HS256",
"typ": "JWT"
}
Ini di-encode pakai Base64URL jadi: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload
Payload berisi claims - data yang kita mau kirim. Misalnya:
{
"sub": "1234567890",
"name": "Luthfi",
"role": "user",
"iat": 1709784000,
"exp": 1709787600
}
Juga di-encode pakai Base64URL. Bukan encrypted. Siapa aja bisa decode ini dan baca isinya.
Coba aja buka jwt.io, paste token di atas, langsung keliatan isinya.
3. Signature
Nah, ini bagian kunci keamanannya.
Signature dibuat dengan cara:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
secret_key ini cuma server yang tahu. Client ga punya, ga bisa lihat, ga bisa nebak.
Kenapa User Ga Bisa Ngubah Isi JWT?
Ini pertanyaan yang bagus dan sering bikin bingung. Karena kalau JWT cuma di-encode (bukan encrypted), berarti siapa aja bisa decode → ubah isinya → encode ulang dong?
Bisa. Tapi sia-sia.
Skenario: user jahat mau upgrade role
Katakanlah user "Budi" dapet JWT setelah login:
{
"sub": "123",
"name": "Budi",
"role": "user"
}
Budi pengen jadi admin. Dia decode payload, ubah "role": "user" jadi "role": "admin", encode ulang pakai Base64URL.
Sekarang dia punya header dan payload baru. Tapi dia ga bisa bikin signature baru yang valid. Kenapa?
Karena signature itu dihitung dari:
HMACSHA256(header + "." + payload, SECRET_KEY)
Budi ga punya SECRET_KEY. Cuma server yang punya.
Jadi yang terjadi:
- Budi kirim JWT dengan payload yang diubah
- Server terima JWT
- Server ambil header + payload dari JWT yang diterima
- Server hitung ulang signature pakai
SECRET_KEYmiliknya - Server bandingin signature yang dia hitung vs signature di JWT
- Ga cocok → Token invalid → Request ditolak
Signature dari Budi: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Signature yang dihitung: Xk9a2PQ7vN3Rt5YmBw1FgH8jK0LzUe4Wd6Ax9Cy3Ds
(Beda! Token ditolak.)
Analogi simpel
Bayangin JWT itu kayak surat resmi dari kantor pemerintah. Isinya bisa dibaca siapa aja (ga dikunci). Tapi di bawah ada stempel resmi yang cuma kantor itu yang punya.
Kamu bisa fotokopi surat, ubah isinya. Tapi kamu ga bisa bikin stempel yang sama. Pas dicek, stempelnya ga cocok = surat palsu.
Signature JWT = stempel itu.
Dua Jenis Algoritma Signing
Symmetric: HMAC (HS256, HS384, HS512)
Satu kunci untuk sign DAN verify.
- Server sign pakai
SECRET_KEY - Server verify juga pakai
SECRET_KEYyang sama - Cocok kalau cuma satu server yang handle semuanya
- Masalah: kalau banyak service perlu verify, semua harus punya
SECRET_KEY. Satu bocor = semua bocor
Asymmetric: RSA / ECDSA (RS256, ES256, dll)
Dua kunci: Private Key untuk sign, Public Key untuk verify.
- Auth server sign pakai Private Key (rahasia, cuma auth server yang punya)
- Service lain verify pakai Public Key (boleh dibagi ke siapa aja)
- Public key bocor? Ga masalah. Ga bisa dipake buat sign token baru
- Ideal untuk microservice architecture
Kelebihan JWT
1. Stateless — ga perlu nyimpan session di server
Ini keunggulan utama JWT dibanding session-based auth.
Session based: user login → server bikin session → simpan di database/Redis → kasih session ID ke client. Setiap request, server lookup session ID di database.
JWT: user login → server bikin token → kasih ke client. Setiap request, server cuma verify signature. Ga perlu lookup ke mana-mana.
graph LR
subgraph Session-Based
A1[Request] --> B1[Lookup DB/Redis] --> C1[Response]
end
subgraph JWT
A2[Request] --> B2[Verify Signature - CPU only] --> C2[Response]
end
Hasilnya? Lebih cepat, lebih scalable.
2. Self-contained — semua info ada di token
Token JWT udah berisi semua informasi yang dibutuhkan. User ID, role, permissions, expiry — semuanya ada di payload.
Server ga perlu query database buat tahu siapa user ini dan boleh ngapain.
3. Cross-domain / Cross-service friendly
JWT bisa dipake lintas domain dan lintas service. Tinggal taro di header Authorization: Bearer <token>.
Ga kayak cookie yang ribet sama domain restriction dan CORS.
4. Scalable secara horizontal
Mau nambah 10 server? Tinggal kasih semua server kunci yang sama (symmetric) atau public key yang sama (asymmetric).
Ga perlu shared session store. Ga perlu sticky sessions. Ga perlu sinkronisasi antar server.
Kekurangan JWT
1. Ga bisa di-revoke dengan gampang
Ini kelemahan terbesar JWT.
Session based: mau kick user? Hapus session dari database. Selesai. Request berikutnya langsung ditolak.
JWT: token udah di tangan client. Server ga nyimpan apa-apa. Gimana caranya invalidate token yang udah beredar?
Ga bisa. Selama token belum expired, token itu valid.
Solusi yang biasa dipake:
- Blacklist: simpan daftar token yang di-revoke di database/Redis. Tapi ini ngalahin purpose JWT yang stateless
- Short expiry + refresh token: bikin access token yang umurnya pendek (misal 15 menit), dan pakai refresh token buat minta yang baru
- Token versioning: simpan versi token di database, kalau mau revoke tinggal naikin versi
Semua solusi di atas nambah complexity. Trade-off.
2. Token size bisa gede
JWT itu self-contained, jadi makin banyak data di payload, makin gede tokennya.
Session ID biasanya cuma ~32 bytes. JWT bisa ratusan bytes, bahkan kilobytes.
Ini dikirim di setiap request. Kalau mobile app dengan koneksi lambat, ini bisa kerasa.
3. Payload bisa dibaca siapa aja
Ingat, JWT cuma di-encode, bukan di-encrypt. Siapa aja bisa decode dan baca isinya.
Jangan pernah taro data sensitif di JWT payload. Jangan taro password, nomor kartu kredit, atau data personal yang sensitif.
Kalau emang harus, pakai JWE (JSON Web Encryption). Tapi ini nambah complexity lagi.
4. Rentan kalau implementasi salah
alg: "none"attack — beberapa library lama terima token tanpa signature kalau header bilang alg none- Secret key yang lemah — kalau pakai HS256 dan secret key-nya
"secret123", bisa di-brute force - Ga validasi issuer dan audience — token dari service A bisa dipake di service B
- Ga cek expiry — token expired tapi tetep diterima
JWT di Microservice Architecture
Di arsitektur monolith, JWT relatif straightforward. Satu server yang sign dan verify. Simple.
Di microservice, ceritanya beda. Ada banyak service yang perlu tahu siapa user ini dan boleh ngapain.
Pendekatan 1: Setiap service verify sendiri
graph LR
Client --> GW[API Gateway]
GW --> A["Service A (verify JWT)"]
GW --> B["Service B (verify JWT)"]
GW --> C["Service C (verify JWT)"]
Pakai asymmetric key (RSA/ECDSA):
- Auth service sign pakai private key
- Semua service verify pakai public key
- Public key bisa dishare lewat JWKS endpoint (JSON Web Key Set)
Kelebihan: setiap service independen, ga perlu call auth service
Kekurangan: revocation tetep susah, token besar jalan-jalan di internal network
Pendekatan 2: API Gateway verify, service trust gateway
graph LR
Client --> GW["API Gateway (verify JWT)"]
GW --> A["Service A (trust gateway)"]
GW --> B["Service B (trust gateway)"]
GW --> C["Service C (trust gateway)"]
Gateway verify JWT sekali, lalu forward request ke service dengan header yang udah di-trust (misal X-User-Id, X-User-Role).
Kelebihan: service di belakang lebih simple, ga perlu logic JWT
Kekurangan: gateway jadi single point of trust. Kalau gateway di-bypass, service ga punya proteksi
Masalah: JWT yang jalan-jalan di internal network
JWT itu self-contained. Di dalamnya ada user info, roles, permissions, claims.
Kalau JWT ini jalan-jalan antar service di internal network:
- Attack surface besar — kalau satu service compromised, attacker dapet JWT lengkap dengan semua claims
- Data leakage — service yang sebenernya ga perlu tahu user details, tetep bisa baca semuanya dari JWT
- Token size — payload gede yang terus-terusan dikirim antar service
Ini yang bikin orang mikirin solusi yang lebih elegan: Phantom Token Pattern.
Phantom Token Pattern
Phantom token itu pattern di mana client dapet opaque token (token yang ga bisa dibaca, kayak random string), tapi di dalam sistem, opaque token itu di-tuker sama JWT yang berisi claims lengkap.
Gimana cara kerjanya?
sequenceDiagram
participant C as Client
participant GW as API Gateway
participant Auth as Auth Server
participant S as Service A
C->>GW: Request + Opaque Token
GW->>Auth: Introspect opaque token
Auth-->>GW: Return JWT
GW->>S: Forward request + JWT
S-->>GW: Response
GW-->>C: Response
Step by step:
- Client login → Auth server kasih opaque token (random string, ga bisa di-decode, ga ada info di dalamnya)
- Client kirim request dengan opaque token di header
Authorization - API Gateway terima opaque token
- Gateway introspect opaque token ke Auth Server: "ini token valid ga? Kalau valid, kasih JWT-nya"
- Auth Server cek di database/cache: "token ini valid, ini JWT-nya"
- Gateway ganti opaque token dengan JWT
- Gateway forward request ke internal service dengan JWT
- Internal service verify dan baca JWT seperti biasa
Kenapa ini bagus?
1. Client ga bisa baca token
Opaque token itu cuma random string. Client ga bisa decode, ga bisa baca claims, ga bisa tahu isinya.
Opaque token: "dGhpcyBpcyBhIHJhbmRvbSBvcGFxdWUgdG9rZW4="
JWT: "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM..."
Client cuma pegang opaque token. Ga ada info yang bisa di-extract.
2. Revocation jadi gampang
Opaque token itu reference token — disimpan di auth server. Mau revoke? Hapus dari database. Selesai.
Pas gateway introspect, auth server bilang "token ini invalid" → request ditolak.
Ini menggabungkan kelebihan session-based (easy revocation) dengan kelebihan JWT (self-contained di internal network).
3. Attack surface berkurang
JWT yang berisi claims sensitif cuma jalan di internal network (antara gateway dan service). Ga pernah keluar ke client atau internet publik.
Kalau ada man-in-the-middle di public network, yang didapet cuma opaque token. Ga ada info yang leaked.
4. Separation of concerns
- Client cuma perlu urusan sama opaque token
- API Gateway urusan introspection dan swap
- Internal services urusan verify JWT
- Auth server urusan manage token lifecycle
Tiap komponen punya tanggung jawab yang jelas.
Trade-off
Ga ada silver bullet. Phantom token juga punya kekurangan:
- Latency tambahan — setiap request, gateway harus introspect ke auth server. Bisa di-mitigate pakai caching, tapi nambah complexity
- Auth server jadi dependency — kalau auth server mati, gateway ga bisa introspect = semua request gagal. Harus high availability
- Complexity — lebih complex dari JWT biasa. Butuh gateway yang support, auth server yang capable, caching strategy yang bener
- Caching dilemma — kalau cache terlalu lama, revocation ga instan. Kalau ga di-cache, auth server kena beban berat tiap request
Kapan pakai phantom token?
- Sistem yang butuh revocation kapan aja (financial, healthcare, enterprise)
- Arsitektur yang udah pakai API Gateway
- Ketika data di JWT sensitif dan ga boleh terekspos ke client
- Organisasi yang butuh compliance ketat (SOC2, HIPAA, PCI-DSS)
Kalau simple app dengan 1-2 service? Overkill. Pakai JWT biasa aja.
Ringkasan
| Aspek | Session-Based | JWT | Phantom Token |
|---|---|---|---|
| Storage | Server-side (DB/Redis) | Client-side | Opaque di client, JWT di internal |
| Scalability | Butuh shared store | Sangat scalable | Tergantung auth server |
| Revocation | Mudah | Susah | Mudah |
| Performance | DB lookup tiap request | CPU-only verify | Introspection + verify |
| Token size | Kecil (session ID) | Bisa besar | Kecil di client, besar di internal |
| Info leakage | Tidak ada | Payload readable | Tidak ada ke client |
| Complexity | Rendah | Sedang | Tinggi |
Kesimpulan
JWT itu powerful dan elegant. Tapi bukan solusi untuk semua masalah.
Yang paling penting dipahami:
- JWT itu di-encode, bukan di-encrypt. Siapa aja bisa baca isinya. Yang bikin aman itu signature-nya
- Signature menjamin integritas. Ubah satu karakter di payload → signature ga cocok → server tahu token udah dimodifikasi
- Stateless itu double-edged sword. Scalable, tapi susah di-revoke
- Di microservice, pilih strategi yang sesuai. Ga harus phantom token, tapi pahami trade-off masing-masing
- Security itu layers. JWT cuma satu layer. Tetep butuh HTTPS, proper key management, input validation, dan security practices lainnya
Jangan cuma pakai JWT karena "semua orang pakai." Pahami cara kerjanya, pahami limitasinya, baru tentukan apa yang paling cocok buat sistem kamu.
