Membangun autentikasi berbasis session dengan Flask-Login, hashing password memakai Werkzeug generate_password_hash, user model yang aman, dan protected routes dengan @login_required serta mekanisme login dan logout.

Sejauh ini siapa pun bisa mengakses semua halaman dan endpoint aplikasi kita. Untuk aplikasi sungguhan — profil pribadi, dashboard, admin — kalian butuh autentikasi: memastikan siapa pengguna yang membuat request. Episode 13 membangun autentikasi berbasis session dengan Flask-Login: login, logout, dan halaman yang hanya bisa diakses pengguna terautentikasi. Ini juga episode di mana keamanan mulai benar-benar diperhitungkan — terutama password hashing, yang kalau salah berarti data pengguna kalian bocor. (Autentikasi API berbasis JWT/OAuth dibahas di episode 18.)
Aturan pertama keamanan password: jangan pernah menyimpan password asli, bahkan di database. Simpan hash-nya. Werkzeug menyediakan fungsi yang tepat:
from werkzeug.security import generate_password_hash, check_password_hash
hash_ = generate_password_hash("rahasia123")
# contoh hasil: scrypt:32768:8:1$xxxxxxxx$...
check_password_hash(hash_, "rahasia123") # True
check_password_hash(hash_, "salah123") # Falsegenerate_password_hash memakai algoritma hash password modern (default Werkzeug 3.x: scrypt) dengan salt acak per user. Ini berarti dua user dengan password sama tetap punya hash berbeda. check_password_hash membandingkan dengan aman (constant-time) sehingga serangan timing tidak bisa dipakai untuk menebak.
Warning
Jangan pernah menulis hashing sendiri (MD5, SHA256 tanpa salt) — algoritma itu bisa di-crack dalam hitungan detik. Gunakan selalu generate_password_hash/check_password_hash dari Werkzeug, atau bcrypt/argon2 dari library khusus. Dan jangan pernah menyimpan password plaintext "untuk sementara" — sekali bocor, seluruh akun terancam.
User model dari episode 10 perlu empat hal untuk Flask-Login:
from flask_login import UserMixin
from datetime import datetime
from app.extensions import db
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password: str) -> None:
self.password_hash = generate_password_hash(password)
def check_password(self, password: str) -> bool:
return check_password_hash(self.password_hash, password)UserMixin memberi method bawaan yang dibutuhkan Flask-Login (is_authenticated, get_id). Kolom yang disimpan hanyalah password_hash — bukan password. Method set_password/check_password membungkus hashing agar dipakai konsisten di seluruh aplikasi.
Install (pip install flask-login) dan daftarkan di factory:
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.login_view = "auth.login" # redirect jika belum login
login_manager.login_message = "Silakan login dulu"def create_app(config) -> Flask:
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
login_manager.init_app(app)
from app.models.user import User
@login_manager.user_loader
def load_user(user_id: str) -> User | None:
return db.session.get(User, int(user_id))
return app@login_manager.user_loader memberitahu Flask-Login cara memuat user dari ID yang tersimpan di session — dipanggil setiap request untuk user yang login.
from app.extensions import db
from app.forms import RegisterForm
from app.models.user import User
@auth_bp.route("/register", methods=["GET", "POST"])
def register():
form = RegisterForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data) # hash, bukan plaintext
db.session.add(user)
db.session.commit()
return redirect(url_for("auth.login"))
return render_template("auth/register.html", form=form)Login: verifikasi kredensial, lalu login_user():
from flask import flash, redirect, url_for
from flask_login import login_user, logout_user
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=form.remember.data)
return redirect(url_for("main.dashboard"))
flash("Username atau password salah")
return render_template("auth/login.html", form=form)
@auth_bp.route("/logout")
def logout():
logout_user()
return redirect(url_for("main.index"))login_user(user, remember=True) mengatur session (opsional cookie "remember me" yang tahan lama). logout_user() membersihkan session. Perhatikan pola verifikasi if user and user.check_password(...): objek user bisa None jika username tidak ada — gabungkan dalam satu kondisi agar tidak membocorkan username mana yang terdaftar.
@main_bp.get("/dashboard")
@login_required
def dashboard():
return render_template("main/dashboard.html", user=current_user)@login_required memeriksa session; jika belum login, pengguna diarahkan ke login_manager.login_view. Di template, current_user selalu tersedia — misalnya untuk menampilkan tombol login/logout sesuai status:
{% if current_user.is_authenticated %}
<p>Halo, {{ current_user.username }}!</p>
<a href="{{ url_for('auth.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>
{% endif %}Sekilas bagaimana Flask-Login bekerja: setelah login_user, Flask menyimpan ID user dalam session — cookie yang ditandatangani dengan SECRET_KEY (episode 8). Karena ditandatangani, isinya tidak bisa dimodifikasi penyerang tanpa kunci. Inilah mengapa SECRET_KEY harus kuat dan rahasia — session cookie yang dipalsukan berarti akses ke akun siapa pun. Catatan penting: session-based auth (episode ini) dan JWT-based auth (episode 18) bukanlah musuh — keduanya menyelesaikan masalah berbeda. Session cocok untuk web app dengan halaman HTML; JWT cocok untuk API yang dikonsumsi SPA dan mobile.
Beberapa praktik pengamanan tambahan:
is_active pada user: user yang diblokir (active=False) otomatis ditolak oleh Flask-Login.SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SAMESITE, dan Secure (episode 20).generate_password_hash.@login_required lupa: setiap route sensitif harus dijaga; jangan hanya mengandalkan sembunyi di URL.logout_user() memecah session; untuk kepastian, pertimbangkan session.clear() pada logout penuh.Pada episode 13 ini, kalian telah membangun autentikasi berbasis session yang utuh.
Inti yang harus dibawa pulang:
generate_password_hash/check_password_hash Werkzeug.login_user/logout_user mengelola session; @login_required melindungi route.current_user tersedia di template untuk UI yang sadar-keadaan login.Di episode 14 selanjutnya, kita kembali ke dunia API: REST API design — konvensi REST, JSON request/response, pagination, API versioning, dan pola endpoint JSON yang konsisten untuk dikonsumsi client. Sampai jumpa di episode 14!