Setelah mempelajari cara membuat REST API dengan FastAPI, langkah berikutnya yang paling penting adalah mengamankan API tersebut. Tanpa autentikasi, siapa pun yang mengetahui alamat endpoint bisa membaca, mengubah, atau menghapus data pengguna. Di tutorial ini kita akan membangun autentikasi lengkap menggunakan JSON Web Token (JWT): mulai dari registrasi pengguna, hashing password, proses login yang menghasilkan token, hingga melindungi endpoint agar hanya bisa diakses oleh pengguna yang sudah login. Semua kode di artikel ini sudah diuji dan bisa langsung kamu jalankan.

Apa Itu JWT dan Mengapa Kamu Membutuhkannya?

Sebelum masuk ke kode, mari pahami dulu konsepnya. Jika kamu masih baru dengan konsep API, silakan baca dulu penjelasan tentang REST API. JWT adalah standar terbuka (RFC 7519) untuk merepresentasikan klaim secara aman antara dua pihak. Sebuah token JWT terdiri dari tiga bagian yang dipisahkan titik: header, payload, dan signature. Server menandatangani token dengan kunci rahasia (secret key), sehingga token tidak bisa dipalsukan oleh pihak lain.

Alur kerjanya sederhana: (1) pengguna login dengan username & password, (2) server memvalidasi kredensial lalu mengembalikan token JWT, (3) pengguna menyimpan token itu dan mengirimkannya di header Authorization: Bearer <token> pada setiap request berikutnya, (4) server memverifikasi tanda tangan token tanpa perlu menyimpan sesi di database. Itulah yang disebut stateless authentication.

Persiapan Proyek dan Instalasi Dependensi

Buat folder proyek baru, aktifkan virtual environment, lalu instal dependensi yang dibutuhkan. Kita memakai python-jose untuk membuat dan memverifikasi JWT, serta passlib dengan bcrypt untuk hashing password.

bash
mkdir jwt-fastapi && cd jwt-fastapi
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn "python-jose[cryptography]" "passlib[bcrypt]"

Jika kamu menemui error seperti password cannot be longer than 72 bytes atau peringatan (trapped) error reading bcrypt version saat menjalankan kode, itu karena ketidakcocokan antara passlib dan bcrypt versi terbaru. Solusinya cukup pin versi bcrypt yang lebih lama: pip install "bcrypt==4.0.1".

Struktur Aplikasi, Konfigurasi, dan Model

Buat file main.py. Pertama, kita tulis semua import, konfigurasi, dan model data. Model BaseModel dari Pydantic akan otomatis memvalidasi request body yang masuk — kamu bisa memperdalam topik ini di artikel tentang Pydantic. Untuk menyederhanakan tutorial, kita pakai dict sebagai pengganti database.

from datetime import datetime, timedelta, timezone

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

# ===== Konfigurasi =====
SECRET_KEY = "ganti-dengan-string-rahasia-yang-panjang-dan-acak"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

app = FastAPI(title="API Catatan dengan JWT Auth")

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
bearer_scheme = HTTPBearer()

# ===== "Database" sederhana (in-memory) =====
users_db: dict = {}
notes_db: dict = {}

# ===== Model Pydantic =====
class UserRegister(BaseModel):
    username: str
    password: str

class UserLogin(BaseModel):
    username: str
    password: str

class NoteCreate(BaseModel):
    content: str

class TokenResponse(BaseModel):
    access_token: str
    token_type: str = "bearer"

Hashing Password dengan bcrypt

Aturan emas keamanan: jangan pernah menyimpan password dalam bentuk teks biasa. Kita menyimpan hasil hash-nya saja. Dengan passlib dan skema bcrypt, proses hashing dan verifikasi jadi sangat mudah.

python
def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

Membuat dan Memverifikasi Token JWT

Selanjutnya kita buat dua fungsi inti: create_access_token untuk menghasilkan token, dan decode_token untuk membaca isinya. Klaim sub (subject) menyimpan username, sedangkan klaim exp menentukan kapan token kedaluwarsa. Token yang sudah lewat masa berlakunya akan otomatis ditolak.

python
def create_access_token(username: str) -> str:
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    payload = {"sub": username, "exp": expire}
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def decode_token(token: str) -> str:
    payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    return payload.get("sub")

Endpoint Register dan Login

Sekarang kita buat endpoint untuk registrasi dan login. Endpoint /register menolak username yang sudah ada, lalu menyimpan hash password-nya. Endpoint /login memvalidasi kredensial dan mengembalikan token JWT jika cocok.

python
@app.post("/register", status_code=201)
def register(payload: UserRegister):
    if payload.username in users_db:
        raise HTTPException(status_code=400, detail="Username sudah terdaftar")
    users_db[payload.username] = {
        "username": payload.username,
        "hashed_password": hash_password(payload.password),
    }
    notes_db[payload.username] = []
    return {"message": "Registrasi berhasil", "username": payload.username}

@app.post("/login", response_model=TokenResponse)
def login(payload: UserLogin):
    user = users_db.get(payload.username)
    if not user or not verify_password(payload.password, user["hashed_password"]):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Username atau password salah",
        )
    token = create_access_token(payload.username)
    return TokenResponse(access_token=token)

Melindungi Endpoint dengan Dependensi

Inilah bagian terpenting. Kita buat dependensi get_current_user yang membaca token dari header Authorization, memverifikasinya, lalu mengembalikan username. Endpoint mana pun yang memakai Depends(get_current_user) otomatis terlindungi: request tanpa token atau dengan token palsu akan ditolak dengan status 401 Unauthorized.

python
def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
) -> str:
    token = credentials.credentials
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Token tidak valid atau sudah kedaluwarsa",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        username = decode_token(token)
    except JWTError:
        raise credentials_exception
    if username is None or username not in users_db:
        raise credentials_exception
    return username

@app.post("/notes", status_code=201)
def create_note(payload: NoteCreate, username: str = Depends(get_current_user)):
    notes_db[username].append(payload.content)
    return {"message": "Catatan tersimpan", "total": len(notes_db[username])}

@app.get("/notes")
def list_notes(username: str = Depends(get_current_user)):
    return {"username": username, "notes": notes_db[username]}

Menjalankan dan Menguji API

Jalankan server dengan uvicorn, lalu buka http://127.0.0.1:8000/docs untuk melihat dokumentasi interaktif Swagger UI yang dibuat FastAPI secara otomatis. Kamu juga bisa menguji endpoint lewat terminal menggunakan curl.

bash
uvicorn main:app --reload
bash
# 1. Registrasi pengguna baru
curl -X POST http://127.0.0.1:8000/register \
  -H "Content-Type: application/json" \
  -d '{"username": "budi", "password": "rahasia123"}'

# 2. Login untuk mendapatkan token
curl -X POST http://127.0.0.1:8000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "budi", "password": "rahasia123"}'

# 3. Akses endpoint tanpa token (seharusnya ditolak: 401)
curl http://127.0.0.1:8000/notes

# 4. Buat catatan dengan token
curl -X POST http://127.0.0.1:8000/notes \
  -H "Authorization: Bearer TOKEN_KAMU" \
  -H "Content-Type: application/json" \
  -d '{"content": "Belajar FastAPI JWT"}'

Beberapa hal yang wajib diperhatikan sebelum dipakai di produksi: (1) ganti SECRET_KEY dengan string acak yang panjang dan simpan di environment variable, jangan di-hardcode; (2) gunakan HTTPS agar token tidak bisa disadap di jaringan; (3) simpan data pengguna di database sungguhan seperti PostgreSQL atau SQLite, bukan dict in-memory; dan (4) pertimbangkan refresh token untuk pengalaman login yang lebih aman dan nyaman.

Kesimpulan

Selamat! Kamu sudah berhasil membangun autentikasi JWT yang lengkap di FastAPI: hashing password dengan bcrypt, register, login, penerbitan token, hingga proteksi endpoint menggunakan dependensi. Konsep yang sama bisa kamu terapkan di proyek nyata — misalnya mengganti dict dengan database, atau menambahkan role/izin per pengguna. Sebagai langkah berikutnya, pelajari membangun aplikasi web dengan Flask untuk melihat pendekatan autentikasi di framework Python lainnya. Selamat mencoba dan semoga bermanfaat!