Pelajari cara membangun sistem login pengguna yang aman dan kuat di Flask dari awal. Panduan komprehensif ini mencakup pengaturan proyek, hashing kata sandi, manajemen sesi, dan praktik keamanan tingkat lanjut.
Otentikasi Flask: Panduan Komprehensif untuk Membangun Sistem Login Pengguna yang Aman
Di dunia digital saat ini, hampir setiap aplikasi web yang berarti membutuhkan cara untuk mengelola dan mengidentifikasi penggunanya. Baik Anda sedang membangun jejaring sosial, platform e-commerce, atau intranet perusahaan, sistem otentikasi yang aman dan andal bukan hanya sekadar fitur—tetapi merupakan persyaratan mendasar. Ini adalah penjaga gerbang digital yang melindungi data pengguna, mempersonalisasi pengalaman, dan memungkinkan kepercayaan.
Flask, micro-framework Python yang populer, memberikan fleksibilitas untuk membangun aplikasi web yang kuat, tetapi dengan sengaja menyerahkan implementasi otentikasi kepada pengembang. Pendekatan minimalis ini adalah kekuatan, memungkinkan Anda untuk memilih alat terbaik untuk pekerjaan itu tanpa terikat pada metodologi tertentu. Namun, itu juga berarti Anda bertanggung jawab untuk membangun sistem dengan benar dan aman.
Panduan komprehensif ini dirancang untuk audiens internasional pengembang. Kami akan memandu Anda melalui setiap langkah membangun sistem login pengguna lengkap yang siap produksi di Flask. Kami akan mulai dengan dasar-dasar mutlak dan secara progresif membangun solusi yang kuat, yang mencakup praktik keamanan penting di sepanjang jalan. Pada akhir tutorial ini, Anda akan memiliki pengetahuan dan kode untuk menerapkan pendaftaran pengguna, login, dan manajemen sesi yang aman dalam proyek Flask Anda sendiri.
Prasyarat: Menyiapkan Lingkungan Pengembangan Anda
Sebelum kita menulis baris pertama kode otentikasi kita, kita perlu membangun lingkungan pengembangan yang bersih dan terorganisir. Ini adalah praktik terbaik universal dalam pengembangan perangkat lunak, memastikan bahwa dependensi proyek Anda tidak bertentangan dengan proyek lain di sistem Anda.
1. Python dan Lingkungan Virtual
Pastikan Anda telah menginstal Python 3.6 atau yang lebih baru di sistem Anda. Kami akan menggunakan lingkungan virtual untuk mengisolasi paket proyek kami. Buka terminal atau command prompt Anda dan jalankan perintah berikut:
# Buat direktori proyek
mkdir flask-auth-project
cd flask-auth-project
# Buat lingkungan virtual (folder 'venv')
python3 -m venv venv
# Aktifkan lingkungan virtual
# Pada macOS/Linux:
source venv/bin/activate
# Pada Windows:
venv\Scripts\activate
Anda akan tahu bahwa lingkungan aktif ketika Anda melihat `(venv)` diawali dengan command prompt Anda.
2. Menginstal Ekstensi Flask Penting
Sistem otentikasi kami akan dibangun di atas tumpukan ekstensi Flask yang sangat baik dan terpelihara dengan baik. Masing-masing melayani tujuan tertentu:
- Flask: Kerangka kerja web inti.
- Flask-SQLAlchemy: Object-Relational Mapper (ORM) untuk berinteraksi dengan database kami dengan cara Pythonic.
- Flask-Migrate: Menangani migrasi skema database.
- Flask-WTF: Menyederhanakan pekerjaan dengan formulir web, menyediakan validasi dan perlindungan CSRF.
- Flask-Login: Mengelola sesi pengguna, menangani login, logout, dan mengingat pengguna.
- Flask-Bcrypt: Menyediakan kemampuan hashing kata sandi yang kuat.
- python-dotenv: Mengelola variabel lingkungan untuk konfigurasi.
Instal semuanya dengan satu perintah:
pip install Flask Flask-SQLAlchemy Flask-Migrate Flask-WTF Flask-Login Flask-Bcrypt python-dotenv
Bagian 1: Fondasi - Struktur Proyek dan Model Database
Proyek yang terorganisir dengan baik lebih mudah dipelihara, diskalakan, dan dipahami. Kami akan menggunakan pola factory aplikasi Flask yang umum.
Merancang Struktur Proyek yang Terukur
Buat direktori dan struktur file berikut di dalam direktori `flask-auth-project` Anda:
/flask-auth-project
|-- /app
| |-- /static
| |-- /templates
| | |-- base.html
| | |-- index.html
| | |-- login.html
| | |-- register.html
| | |-- dashboard.html
| |-- __init__.py
| |-- models.py
| |-- forms.py
| |-- routes.py
|-- .env
|-- config.py
|-- run.py
- /app: Paket utama yang berisi logika aplikasi kita.
- /templates: Akan menyimpan file HTML kita.
- __init__.py: Menginisialisasi aplikasi Flask kita (factory aplikasi).
- models.py: Mendefinisikan tabel database kita (mis., model Pengguna).
- forms.py: Mendefinisikan formulir pendaftaran dan login kita menggunakan Flask-WTF.
- routes.py: Berisi fungsi tampilan kita (logika untuk URL yang berbeda).
- config.py: Menyimpan pengaturan konfigurasi aplikasi.
- run.py: Skrip utama untuk memulai server web.
- .env: File untuk menyimpan variabel lingkungan seperti kunci rahasia (file ini SEBAIKNYA TIDAK di-commit ke version control).
Mengonfigurasi Aplikasi Flask Anda
Mari kita isi file konfigurasi kita.
file .env:
Buat file ini di root proyek Anda. Di sinilah kita akan menyimpan informasi sensitif.
SECRET_KEY='a-very-strong-and-long-random-secret-key'
DATABASE_URL='sqlite:///site.db'
PENTING: Ganti nilai `SECRET_KEY` dengan string acak yang panjang, acak, dan tidak dapat diprediksi milik Anda sendiri. Kunci ini sangat penting untuk mengamankan sesi pengguna.
file config.py:
File ini membaca konfigurasi dari file `.env` kita.
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
Membuat Model Pengguna dengan Flask-SQLAlchemy
Model Pengguna adalah inti dari sistem otentikasi kita. Ini mendefinisikan struktur tabel `users` di database kita.
app/models.py:
from flask_login import UserMixin
from . import db, login_manager
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
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(128), nullable=False)
def __repr__(self):
return f'<User {self.username}>'
Mari kita uraikan ini:
- `UserMixin`: Ini adalah kelas dari `Flask-Login` yang menyertakan implementasi generik untuk metode seperti `is_authenticated`, `is_active`, dll., yang dibutuhkan oleh model Pengguna kita.
- `@login_manager.user_loader`: Fungsi ini adalah persyaratan untuk `Flask-Login`. Ini digunakan untuk memuat ulang objek pengguna dari ID pengguna yang disimpan dalam sesi. Flask-Login akan memanggil fungsi ini pada setiap permintaan untuk pengguna yang masuk.
- `password_hash`: Perhatikan bahwa kita TIDAK menyimpan kata sandi secara langsung. Kami menyimpan `password_hash`. Ini adalah salah satu prinsip keamanan paling penting dalam otentikasi. Menyimpan kata sandi teks biasa adalah kerentanan keamanan yang sangat besar. Jika database Anda pernah disusupi, penyerang akan memiliki kata sandi setiap pengguna. Dengan menyimpan hash, Anda membuatnya secara komputasi tidak mungkin bagi mereka untuk mengambil kata sandi asli.
Menginisialisasi Aplikasi
Sekarang, mari kita satukan semuanya di factory aplikasi kita.
app/__init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from config import Config
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
login_manager.login_view = 'main.login' # Halaman pengalihan untuk pengguna yang tidak masuk
login_manager.login_message_category = 'info' # Kelas Bootstrap untuk pesan
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
run.py:
from app import create_app, db
from app.models import User
app = create_app()
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User}
if __name__ == '__main__':
app.run(debug=True)
Sebelum kita dapat menjalankan aplikasi, kita perlu membuat database. Dari lingkungan virtual Anda yang diaktifkan di terminal, jalankan perintah-perintah ini:
flask shell
>>> from app import db
>>> db.create_all()
>>> exit()
Ini akan membuat file `site.db` di direktori root Anda, yang berisi tabel `user` yang kita definisikan.
Bagian 2: Membangun Logika Otentikasi Inti
Dengan fondasi yang sudah ada, kita sekarang dapat membangun bagian-bagian yang menghadap pengguna: formulir pendaftaran dan login serta rute yang memprosesnya.
Pendaftaran Pengguna: Mendaftarkan Pengguna Baru dengan Aman
Pertama, kita mendefinisikan formulir menggunakan Flask-WTF.
app/forms.py:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from .models import User
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is already registered. Please choose a different one.')
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
Perhatikan validator khusus `validate_username` dan `validate_email`. Flask-WTF secara otomatis memanggil metode apa pun dengan pola `validate_<field_name>` dan menggunakannya sebagai validator khusus untuk bidang tersebut. Inilah cara kita memeriksa apakah nama pengguna atau email sudah ada di database.
Selanjutnya, kita membuat rute untuk menangani pendaftaran.
app/routes.py:
from flask import Blueprint, render_template, url_for, flash, redirect, request
from .forms import RegistrationForm, LoginForm
from .models import User
from . import db, bcrypt
from flask_login import login_user, current_user, logout_user, login_required
main = Blueprint('main', __name__)
@main.route('/')
@main.route('/index')
def index():
return render_template('index.html')
@main.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password_hash=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('main.login'))
return render_template('register.html', title='Register', form=form)
Hashing Kata Sandi dengan Flask-Bcrypt
Baris terpenting dalam kode di atas adalah:
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
Bcrypt adalah algoritma hashing adaptif modern. Ini mengambil kata sandi pengguna dan melakukan transformasi satu arah yang kompleks dan mahal secara komputasi. Ini juga menggabungkan "garam" acak untuk setiap kata sandi untuk mencegah serangan tabel pelangi. Ini berarti bahwa bahkan jika dua pengguna memiliki kata sandi yang sama, hash yang disimpan akan sangat berbeda. Hash yang dihasilkan adalah yang kita simpan di database. Hampir tidak mungkin untuk membalikkan proses ini untuk mendapatkan kata sandi asli.
Login Pengguna: Mengotentikasi Pengguna yang Ada
Sekarang, mari kita tambahkan rute login ke file `app/routes.py` kita.
app/routes.py (tambahkan rute ini):
@main.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password_hash, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('main.index'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
Langkah-langkah utama di sini adalah:
- Temukan pengguna: Kita menanyakan database untuk pengguna dengan alamat email yang dikirimkan.
- Verifikasi kata sandi: Ini adalah pemeriksaan penting: `bcrypt.check_password_hash(user.password_hash, form.password.data)`. Fungsi ini mengambil hash yang disimpan dari database kita dan kata sandi teks biasa yang baru saja dimasukkan pengguna. Ini menghash ulang kata sandi yang dikirimkan menggunakan garam yang sama (yang disimpan sebagai bagian dari hash itu sendiri) dan membandingkan hasilnya. Ini mengembalikan `True` hanya jika cocok. Ini memungkinkan kita untuk memverifikasi kata sandi tanpa perlu mendekripsi hash yang disimpan.
- Kelola sesi: Jika kata sandi benar, kita memanggil `login_user(user, remember=form.remember.data)`. Fungsi ini dari `Flask-Login` mendaftarkan pengguna sebagai masuk, menyimpan ID mereka dalam sesi pengguna (cookie sisi server yang aman). Argumen `remember` menangani fungsionalitas "Ingat Saya".
Logout Pengguna: Mengakhiri Sesi dengan Aman
Logout sangat mudah. Kita hanya membutuhkan rute yang memanggil fungsi `logout_user` Flask-Login.
app/routes.py (tambahkan rute ini):
@main.route('/logout')
def logout():
logout_user()
return redirect(url_for('main.index'))
Fungsi ini akan menghapus ID pengguna dari sesi, secara efektif mengeluarkan mereka.
Bagian 3: Melindungi Rute dan Mengelola Sesi Pengguna
Sekarang pengguna dapat masuk dan keluar, kita perlu memanfaatkan status terotentikasi mereka.
Melindungi Konten dengan `@login_required`
Banyak halaman, seperti dasbor pengguna atau pengaturan akun, hanya boleh dapat diakses oleh pengguna yang masuk. `Flask-Login` membuat ini sangat sederhana dengan dekorator `@login_required`.
Mari kita buat rute dasbor yang dilindungi.
app/routes.py (tambahkan rute ini):
@main.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', title='Dashboard')
Itu saja! Jika pengguna yang tidak masuk mencoba mengunjungi `/dashboard`, `Flask-Login` akan secara otomatis mencegat permintaan dan mengarahkannya ke halaman login (yang kita konfigurasi di `app/__init__.py` dengan `login_manager.login_view = 'main.login'`). Setelah mereka berhasil masuk, itu akan secara cerdas mengarahkan mereka kembali ke halaman dasbor yang awalnya ingin mereka akses.
Mengakses Informasi Pengguna Saat Ini
Di dalam rute dan templat Anda, `Flask-Login` menyediakan objek proxy ajaib yang disebut `current_user`. Objek ini mewakili pengguna yang saat ini masuk untuk permintaan aktif. Jika tidak ada pengguna yang masuk, itu adalah objek pengguna anonim di mana `current_user.is_authenticated` akan menjadi `False`.
Anda dapat menggunakan ini dalam kode Python Anda:
# Di dalam rute
if current_user.is_authenticated:
print(f'Hello, {current_user.username}!')
Dan Anda juga dapat menggunakannya langsung di templat Jinja2 Anda:
<!-- Di dalam templat seperti base.html -->
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
<a href="{{ url_for('main.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('main.login') }}">Login</a>
<a href="{{ url_for('main.register') }}">Register</a>
{% endif %}
Ini memungkinkan Anda untuk secara dinamis mengubah bilah navigasi atau bagian lain dari UI Anda berdasarkan status login pengguna.
Templat HTML
Untuk kelengkapan, berikut adalah beberapa templat dasar yang dapat Anda tempatkan di direktori `app/templates`. Mereka menggunakan HTML sederhana tetapi dapat dengan mudah diintegrasikan dengan kerangka kerja seperti Bootstrap atau Tailwind CSS.
base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }} - Flask Auth App</title>
</head>
<body>
<nav>
<a href="{{ url_for('main.index') }}">Home</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
<a href="{{ url_for('main.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('main.login') }}">Login</a>
<a href="{{ url_for('main.register') }}">Register</a>
{% endif %}
</nav>
<hr>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
register.html / login.html (contoh menggunakan formulir pendaftaran):
{% extends "base.html" %}
{% block content %}
<div>
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset>
<legend>Join Today</legend>
<div>
{{ form.username.label }}
{{ form.username() }}
</div>
<div>
{{ form.email.label }}
{{ form.email() }}
</div>
<div>
{{ form.password.label }}
{{ form.password() }}
</div>
<div>
{{ form.confirm_password.label }}
{{ form.confirm_password() }}
</div>
</fieldset>
<div>
{{ form.submit() }}
</div>
</form>
</div>
{% endblock content %}
Bagian 4: Topik Lanjutan dan Praktik Terbaik Keamanan
Sistem yang telah kita bangun solid, tetapi aplikasi tingkat produksi membutuhkan lebih banyak. Berikut adalah langkah selanjutnya yang penting dan pertimbangan keamanan.
1. Fungsionalitas Reset Kata Sandi
Pengguna pasti akan melupakan kata sandi mereka. Alur reset kata sandi yang aman sangat penting. Proses standar yang aman adalah:
- Pengguna memasukkan alamat email mereka di halaman "Lupa Kata Sandi".
- Aplikasi menghasilkan token sekali pakai yang aman dan sensitif terhadap waktu. Pustaka `itsdangerous` (diinstal dengan Flask) sangat cocok untuk ini.
- Aplikasi mengirim email ke pengguna yang berisi tautan dengan token ini.
- Ketika pengguna mengklik tautan, aplikasi memvalidasi token (memeriksa validitas dan kedaluwarsanya).
- Jika valid, pengguna akan disajikan dengan formulir untuk memasukkan dan mengonfirmasi kata sandi baru.
Jangan pernah mengirim email kata sandi lama pengguna atau kata sandi teks biasa baru.
2. Konfirmasi Email pada Pendaftaran
Untuk mencegah pengguna mendaftar dengan alamat email palsu dan untuk memastikan Anda dapat menghubungi mereka, Anda harus menerapkan langkah konfirmasi email. Prosesnya sangat mirip dengan reset kata sandi: buat token, kirim email tautan konfirmasi, dan miliki rute yang memvalidasi token dan menandai akun pengguna sebagai `confirmed` di database.
3. Pembatasan Laju untuk Mencegah Serangan Brute-Force
Serangan brute-force adalah ketika penyerang berulang kali mencoba kata sandi yang berbeda pada formulir login. Untuk mengurangi ini, Anda harus menerapkan pembatasan laju. Ini membatasi jumlah upaya login yang dapat dilakukan satu alamat IP dalam jangka waktu tertentu (mis., 5 upaya gagal per menit). Ekstensi `Flask-Limiter` adalah alat yang sangat baik untuk ini.
4. Menggunakan Variabel Lingkungan untuk Semua Rahasia
Kita sudah melakukan ini untuk `SECRET_KEY` dan `DATABASE_URL` kita, yang bagus. Ini adalah praktik penting untuk keamanan dan portabilitas. Jangan pernah melakukan commit file `.env` Anda atau file apa pun dengan kredensial yang dikodekan secara permanen (seperti kunci API atau kata sandi database) ke sistem kontrol versi publik seperti GitHub. Selalu gunakan file `.gitignore` untuk mengecualikannya.
5. Perlindungan Cross-Site Request Forgery (CSRF)
Kabar baik! Dengan menggunakan `Flask-WTF` dan menyertakan `{{ form.hidden_tag() }}` dalam formulir kita, kita telah mengaktifkan perlindungan CSRF. Tag tersembunyi ini menghasilkan token unik untuk setiap pengiriman formulir, memastikan bahwa permintaan berasal dari situs Anda yang sebenarnya dan bukan dari sumber eksternal jahat yang mencoba menipu pengguna Anda.
Kesimpulan: Langkah Selanjutnya Anda dalam Otentikasi Flask
Selamat! Anda telah berhasil membangun sistem otentikasi pengguna yang lengkap dan aman di Flask. Kami telah membahas seluruh siklus hidup: menyiapkan proyek yang terukur, membuat model database, menangani pendaftaran pengguna dengan aman dengan hashing kata sandi, mengotentikasi pengguna, mengelola sesi dengan Flask-Login, dan melindungi rute.
Anda sekarang memiliki fondasi yang kuat yang dapat Anda integrasikan dengan percaya diri ke dalam proyek Flask apa pun. Ingatlah bahwa keamanan adalah proses berkelanjutan, bukan penyiapan satu kali. Prinsip-prinsip yang telah kita diskusikan—terutama hashing kata sandi dan melindungi kunci rahasia—tidak dapat dinegosiasikan untuk aplikasi apa pun yang menangani data pengguna.
Dari sini, Anda dapat menjelajahi topik otentikasi yang lebih canggih untuk lebih meningkatkan aplikasi Anda:
- Role-Based Access Control (RBAC): Tambahkan bidang `role` ke model Pengguna Anda untuk memberikan izin yang berbeda kepada pengguna biasa dan administrator.
- Integrasi OAuth: Izinkan pengguna untuk masuk menggunakan layanan pihak ketiga seperti Google, GitHub, atau Facebook.
- Two-Factor Authentication (2FA): Tambahkan lapisan keamanan ekstra dengan memerlukan kode dari aplikasi autentikator atau SMS.
Dengan menguasai dasar-dasar otentikasi, Anda telah mengambil langkah maju yang signifikan dalam perjalanan Anda sebagai pengembang web profesional. Selamat membuat kode!