Mengamankan API modern dengan Flask-JWT-Extended: access token dan refresh token, RBAC untuk kontrol akses berbasis peran, serta social login dengan Authlib OAuth untuk autentikasi via Google dan GitHub yang dikonsumsi SPA dan mobile.

Session-based auth (episode 13) cocok untuk halaman web klasik, tetapi API yang dikonsumsi SPA dan mobile butuh pendekatan berbeda: token. Episode 18 membahas JWT (JSON Web Token) dengan Flask-JWT-Extended — access token dan refresh token, kontrol akses berbasis peran (RBAC) — lalu menutup dengan OAuth social login menggunakan Authlib. Prinsipnya: token stateless (server tidak menyimpan session), dikirim client di header Authorization, dan bisa diverifikasi tanpa database query per request.
Install dan konfigurasi:
pip install flask-jwt-extendedfrom flask_jwt_extended import JWTManager
jwt = JWTManager()
def create_app(config) -> Flask:
app = Flask(__name__)
app.config.from_object(config)
jwt.init_app(app)
return appPraktik penting pada konfigurasi: access token pendek (15 menit) sehingga jika bocor, jendela penyalahgunaannya sempit; refresh token panjang untuk memperoleh access token baru. Set JWT_SECRET_KEY kuat dari environment (episode 8), serta JWT_ACCESS_TOKEN_EXPIRES dan JWT_REFRESH_TOKEN_EXPIRES sesuai kebijakan.
from flask_jwt_extended import create_access_token, create_refresh_token
@api_bp.post("/auth/login")
def login() -> tuple[dict, int]:
data = request.get_json(silent=True) or {}
user = User.query.filter_by(username=data.get("username")).first()
if not user or not user.check_password(data.get("password", "")):
return api_error("kredensial salah", 401)
access_token = create_access_token(identity=str(user.id))
refresh_token = create_refresh_token(identity=str(user.id))
return api_ok({"access_token": access_token, "refresh_token": refresh_token})Endpoint API dilindungi dengan @jwt_required():
from flask_jwt_extended import jwt_required, get_jwt_identity
@api_bp.get("/users/me")
@jwt_required()
def me() -> tuple[dict, int]:
user_id = get_jwt_identity()
user = db.session.get(User, int(user_id))
return api_ok({"id": user.id, "username": user.username, "email": user.email})@jwt_required() memeriksa dan memvalidasi access token; get_jwt_identity() mengembalikan identitas (user id) yang disimpan saat login. Token tidak valid/kadaluarsa otomatis menghasilkan 401.
Saat access token kadaluarsa, client meminta yang baru dengan refresh token:
from flask_jwt_extended import jwt_required, create_access_token
@api_bp.post("/auth/refresh")
@jwt_required(refresh=True)
def refresh() -> tuple[dict, int]:
user_id = get_jwt_identity()
access_token = create_access_token(identity=user_id)
return api_ok({"access_token": access_token})@jwt_required(refresh=True) menerima refresh token, bukan access token. Dengan pola ini, client otomatis menyegarkan access token tanpa login ulang selama refresh token belum kadaluarsa.
Token bisa membawa klaim peran untuk kontrol akses. Di endpoint login, tambahkan klaim saat membuat token:
access_token = create_access_token(
identity=str(user.id),
additional_claims={"role": user.role}, # "admin" | "user"
)Lalu verifikasi peran di endpoint yang butuh hak admin:
from functools import wraps
from flask_jwt_extended import get_jwt
def admin_required(f):
@wraps(f)
@jwt_required()
def wrapper(*args, **kwargs):
claims = get_jwt()
if claims.get("role") != "admin":
return api_error("butuh hak admin", 403)
return f(*args, **kwargs)
return wrapperNote
Ingat bahwa JWT adalah stateless — pencabutan (revocation) tidak instan seperti menghapus session. Jika user harus dicabut aksesnya seketika (akun di-ban), gunakan daftar hitam (JWT blocklist) yang dicek @jwt_required, atau pakai session-based auth untuk kasus yang butuh revoke cepat. Flask-JWT-Extended menyediakan blocklist hooks.
Untuk login "via Google/GitHub", aplikasi memakai OAuth 2.0: aplikasi meminta akses atas nama user tanpa melihat password-nya. Authlib menyederhanakannya:
pip install authlibfrom authlib.integrations.flask_client import OAuth
oauth = OAuth()
oauth.register(
name="google",
client_id=os.environ.get("GOOGLE_CLIENT_ID"),
client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"),
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
)Alur loginnya:
@auth_bp.get("/login/google")
def google_login():
return oauth.google.authorize_redirect(url_for("auth.google_callback", _external=True))
@auth_bp.get("/callback/google")
def google_callback():
token = oauth.google.authorize_access_token()
userinfo = oauth.google.userinfo()
user = find_or_create_user(userinfo["email"], userinfo["name"])
login_user(user)
return redirect(url_for("main.index"))User yang sudah terverifikasi oleh Google tidak perlu password lagi di aplikasi kalian — alur ini memakai OpenID Connect (OAuth 2.0 + profile). Client ID/secret dari penyedia (Google, GitHub) di-set sebagai environment variable (episode 8) — jangan pernah di-commit.
JWT_SECRET_KEY sama dengan SECRET_KEY lemah: kunci harus acak & rahasia (episode 8)._external=True: URL callback salah di produksi.Pada episode 18 ini, kalian telah mengamankan API dengan JWT dan OAuth.
Inti yang harus dibawa pulang:
@jwt_required() melindungi endpoint; get_jwt_identity() mengenali user.additional_claims + verifikasi get_jwt() per endpoint.localStorage.Di episode 19 selanjutnya, kita menangani salah satu fitur paling rawan: upload, file & content security — validasi file, secure filenames, batas ukuran, dan praktik aman untuk avatar dan attachment. Sampai jumpa di episode 19!