Autentikasi memverifikasi siapa yang mengirim request.
Token-based auth (paling umum untuk API):
1. Client login → POST /auth/login (email + password)
2. Server verifikasi → return token
3. Client simpan token
4. Setiap request: Authorization: Bearer <token>
5. Server verifikasi token → beri akses
Login endpoint:
app.post("/auth/login", (req, res) => {
const { email, password } = req.body;
const user = db.prepare("SELECT * FROM users WHERE email = ?").get(email);
if (!user || user.password !== hashPassword(password)) {
return res.status(401).json({ error: "Email atau password salah" });
}
const token = generateToken(user.id);
res.json({ token, user: { id: user.id, name: user.name } });
});
Auth middleware:
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Token required" });
const userId = verifyToken(token);
if (!userId) return res.status(401).json({ error: "Invalid token" });
req.userId = userId; // Tersedia di handler berikutnya
next();
}
// Protected route
app.get("/profile", requireAuth, (req, res) => {
const user = getUserById(req.userId);
res.json(user);
});
401 vs 403:
- 401 Unauthorized — belum login / token invalid
- 403 Forbidden — sudah login tapi tidak punya akses
JWT Deep Dive
Contoh di atas pakai "opaque token" generik. Paling umum di industri: JWT (JSON Web Token). JWT adalah string self-contained 3 bagian dipisah titik:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjMiLCJuYW1lIjoiQnVkaSIsImV4cCI6MTcxNDYxMjM0NX0 . SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header (base64) Payload (base64 — decodable!) Signature (verified by server)
Header — algoritma + tipe:
{"alg": "HS256", "typ": "JWT"}
Payload — klaim user:
{
"sub": "123", // subject (user id)
"name": "Budi",
"role": "admin",
"iat": 1714611345, // issued at
"exp": 1714697745 // expires at (~24 jam)
}
Signature — HMAC-SHA256(base64(header) + "." + base64(payload), SECRET_KEY). Siapapun yang tidak punya SECRET_KEY tidak bisa buat JWT valid.
// Server membuat JWT
import jwt from "jsonwebtoken";
const token = jwt.sign(
{ sub: user.id, name: user.name, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "24h" }
);
// Server verifikasi JWT
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// decoded.sub berisi user id
} catch (err) {
// Invalid atau expired
}
⚠️ JWT pitfalls penting:
- Payload base64, bukan encrypted. Siapapun bisa decode dan lihat isinya. Jangan taruh data sensitif (password hash, internal ID, etc.).
- Tidak bisa di-revoke di server tanpa state tambahan. Kalau user logout, JWT lama masih valid sampai expire. Solusi: blacklist Redis, atau pakai refresh token pattern.
alg: noneattack. Beberapa library buggy menerima JWT tanpa signature. Selalu pakai library terupdate + verify algoritma.- Secret management. SECRET_KEY minimal 256-bit random. Kalau bocor, attacker bisa buat JWT palsu.
Refresh Token Pattern
JWT umur pendek (15 menit). Refresh token umur panjang (7–30 hari), disimpan di httpOnly cookie.
Login:
Server → { accessToken: JWT (15min), refreshToken: UUID (30d, httpOnly cookie) }
Client hit API:
Header: Authorization: Bearer <accessToken>
Kalau 401 → POST /auth/refresh (cookie ke-include otomatis)
→ Server verifikasi refresh token di DB → return new access token
→ Retry request asli
Trade-off: access token bocor (misal di log) dampaknya terbatas 15 menit, dan logout bisa di-enforce dengan menghapus refresh token di DB.
OAuth 2.0 — Untuk Third-Party Login
OAuth 2.0 memungkinkan user login dengan akun Google/GitHub/Facebook tanpa share password. Authorization Code Flow adalah yang paling umum:
1. User klik "Login with Google" di aplikasi kamu
2. Redirect ke Google: accounts.google.com/o/oauth2/v2/auth?
client_id=YOUR_ID
&redirect_uri=https://app.kamu/auth/callback
&scope=openid email profile
&state=RANDOM_CSRF_TOKEN
3. User login di Google + approve scope
4. Google redirect back: https://app.kamu/auth/callback?
code=AUTHORIZATION_CODE
&state=RANDOM_CSRF_TOKEN ← kamu verify state untuk prevent CSRF
5. Server kamu exchange code for token:
POST https://oauth2.googleapis.com/token
{ code, client_id, client_secret, redirect_uri, grant_type: "authorization_code" }
6. Response: { access_token, id_token, refresh_token }
7. Server decode id_token (JWT dari Google) → dapat user info
8. Buat session / JWT di aplikasi kamu
PKCE (Proof Key for Code Exchange) — wajib untuk SPA/mobile app:
- Client generate
code_verifier(random 43–128 char) +code_challenge(SHA256-base64 dari verifier) - Kirim
code_challengedi step 2,code_verifierdi step 5 - Mencegah attacker mencegat
codedan menukar tanpa tahu verifier
API Keys — Server-to-Server
Untuk komunikasi backend-to-backend (tidak ada user browser):
Client: GET /api/data
Header: X-API-Key: sk_live_abc123xyz789
Best practice:
- Generate random 32+ char dengan
crypto.randomBytes - Hash sebelum simpan — kalau DB bocor, attacker tidak dapat key asli
- Scope per key — "read-only", "write", "admin"
- Rotasi berkala — expire 90 hari, biar user rotate. Jangan lupa grace period 7 hari biar migration lancar
- Per-key rate limit — monitor usage, detect anomali
- Revokable — simpan di DB supaya bisa langsung invalidate
Matrix Pemilihan Auth Method
| Use case | Metode | Alasan |
|---|---|---|
| Web app dengan cookie | Session | Simple, native browser support |
| SPA / Mobile | JWT + refresh token | Stateless, cocok cross-domain |
| Microservice internal | mTLS atau JWT signed | Infrastructure-level trust |
| Third-party integration | OAuth 2.0 | Standard, user consent jelas |
| Server-to-server (B2B) | API Key | Simple, tidak ada user context |
| Banking / finansial | OAuth + FAPI | Regulated, high-security |