Flask Authentication: Login, Session, dan Logout
Authentication menjawab satu pertanyaan penting pada setiap request: siapa user yang sedang memakai aplikasi? Jawabannya tidak cukup dicari sekali saat form login dikirim. Aplikasi perlu mengingat identitas tersebut untuk request berikutnya, membatasi halaman tertentu, dan menghapusnya kembali ketika user logout.
Di Flask, identitas kecil seperti user_id bisa disimpan di session. Browser membawa cookie session pada request berikutnya, lalu Flask memverifikasi dan membaca isinya. Password tidak ikut disimpan di sana; password hanya dipakai sebentar untuk dibandingkan dengan hash di database.
Simpan Hash, Bukan Password Asli
Tambahkan field email dan password_hash pada model User. Method kecil di bawah ini menjaga proses membuat dan memeriksa hash tetap berada di satu tempat:
# File: models.py
from werkzeug.security import check_password_hash, generate_password_hash
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
generate_password_hash() menghasilkan nilai berbeda dari password asli dan menyertakan salt. Saat login, check_password_hash() memeriksa input tanpa perlu mengembalikan hash menjadi password semula.
Jangan memakai SHA-256 biasa secara langsung untuk password. Password hashing membutuhkan algoritma yang sengaja dibuat lambat dan memiliki salt agar serangan tebakan massal lebih mahal.
Login dan Logout dalam Satu Blueprint
Route login melayani dua kebutuhan: GET menampilkan form, sedangkan POST memeriksa kredensial.
# File: routes/auth.py
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from models import User, db
auth_bp = Blueprint("auth", __name__)
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
email = request.form.get("email", "").strip().lower()
password = request.form.get("password", "")
query = db.select(User).where(User.email == email)
user = db.session.execute(query).scalar_one_or_none()
if user is None or not user.check_password(password):
flash("Email atau password tidak cocok.", "error")
else:
session.clear()
session["user_id"] = user.id
return redirect(url_for("notes.index"))
return render_template("auth/login.html")
@auth_bp.post("/logout")
def logout():
session.clear()
return redirect(url_for("auth.login"))
Email dinormalisasi dengan .strip().lower() agar spasi dan perbedaan huruf besar tidak membuat pencarian gagal. Pesan error sengaja dibuat sama untuk email yang tidak terdaftar dan password yang salah. Dengan begitu, response tidak membocorkan akun mana yang ada di database.
session.clear() sebelum menyimpan user_id membersihkan isi session lama. Setelah login sukses, session hanya memegang identitas minimum yang dibutuhkan. Data user lain tetap dibaca dari database, sehingga perubahan nama atau status akun tidak tertinggal di cookie.
Logout memakai POST karena ia mengubah keadaan session. Di template, tombolnya dapat dibuat sebagai form kecil:
<!-- File: templates/auth/login.html -->
<h1>Login</h1>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% endwith %}
<form method="post" action="{{ url_for('auth.login') }}">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required>
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button type="submit">Login</button>
</form>
Muat User pada Setiap Request
Session menyimpan ID, bukan object User. Gunakan before_app_request untuk mencari user satu kali di awal request dan menaruhnya di g.user:
# File: routes/auth.py
from flask import g
@auth_bp.before_app_request
def load_logged_in_user():
user_id = session.get("user_id")
g.user = db.session.get(User, user_id) if user_id is not None else None
Object g hidup hanya selama satu request. Template dan route bisa membaca g.user, tetapi nilainya tidak disimpan untuk request berikutnya. Kalau user sudah dihapus dari database, hasil query menjadi None dan aplikasi tidak menganggap session lama sebagai akun yang valid.
Lindungi Route yang Membutuhkan Login
Decorator berikut mengarahkan guest ke halaman login:
# File: auth_helpers.py
from functools import wraps
from flask import g, redirect, url_for
def login_required(view):
@wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for("auth.login"))
return view(**kwargs)
return wrapped_view
Pasang decorator setelah decorator route:
# File: routes/notes.py
from auth_helpers import login_required
@notes_bp.get("")
@login_required
def index():
return "Halaman notes milik user yang sudah login"
Urutan decorator penting. Flask lebih dulu mendaftarkan hasil dari login_required, sehingga pengecekan dijalankan setiap kali route dipanggil.
Batas Contoh Ini
Alur di atas sudah menunjukkan password hash, session, current user, protected route, dan logout. Untuk production, masih ada pekerjaan tambahan: gunakan SECRET_KEY acak dari environment, aktifkan HTTPS dan cookie yang aman, tambahkan CSRF protection, rate limit percobaan login, serta mekanisme reset password.
Saat mencobanya, pastikan password mentah tidak pernah masuk ke log atau database. Coba juga mengakses /notes sebelum login, setelah login, dan sesudah logout. Tiga keadaan itu akan menunjukkan dengan jelas bagaimana session memengaruhi request berikutnya.
Artikel Terkait
Lanjutkan membaca topik yang masih satu konteks.
Flask CRUD SQLAlchemy: Create, Read, Update, Delete
Bangun CRUD Flask dan SQLAlchemy untuk membuat, membaca, mengubah, serta menghapus note lengkap dengan validasi, form, dan redirect.
Flask SQLAlchemy: Koneksi Database dan Model
Hubungkan Flask ke SQLite dengan Flask-SQLAlchemy, buat model dan tabel, lalu pahami proses add, commit, serta query data.
Struktur Project Flask: Blueprint, Template, dan Config
Rapikan struktur project Flask dengan application factory, Blueprint, folder route, template, static files, dan config yang terpisah.
Flask Static Files: CSS, JavaScript, dan Asset
Atur static files Flask untuk CSS, JavaScript, dan gambar, lalu panggil setiap asset dengan url_for agar path tetap mudah dirawat.