Membedah autentikasi dan otorisasi Rails: has_secure_password untuk password hashing, session & cookies, Rails Authentication generator dan Devise sebagai alternatif, serta Pundit untuk policies dan role-based access control

Setelah di episode 8 kalian mengelola data dari form, episode 9 membahas dua konsep yang sering tertukar namun sama-sama krusial: authentication (siapa kalian) dan authorization (apa yang boleh kalian lakukan). Auth adalah pintu masuk — tanpa autentikasi yang benar, semua fitur lain bisa diakses orang asing.
Mengapa episode ini penting? Karena autentikasi melibatkan banyak keputusan yang sering salah dipahami: di mana password di-hash, bagaimana session disimpan, dan bagaimana membedakan peran pengguna. Rails 8 memberikan alat modern untuk ini — has_secure_password, session berbasis cookie, dan generator Authentication bawaan — dan episode ini menjelaskan mengapa desainnya seperti itu, bukan sekadar memakai gem.
Dasar autentikasi Rails adalah has_secure_password — makro yang menangani hashing password dengan bcrypt:
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }, if: -> { password.present? }
endYang disediakan has_secure_password:
password_digest di tabel (bukan password).password= dan password_confirmation= (opsional, untuk konfirmasi).authenticate(password) yang memverifikasi hash — mengembalikan user atau false.Kuncinya: database hanya menyimpan password_digest — hasil bcrypt yang irreversible. Sekalipun database bocor, password asli tidak bisa dibaca. Ini standar yang wajib ada di aplikasi apa pun.
Setelah login, Rails perlu mengingat siapa user di request berikutnya — lewat session yang disimpan di cookie (tertanda dan terenkripsi):
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path, notice: "Login berhasil"
else
flash.now[:alert] = "Email atau password salah"
render :new, status: :unprocessable_entity
end
end
def destroy
session.delete(:user_id)
redirect_to login_path, notice: "Logout berhasil"
endsession[:user_id] = user.id menyimpan ID di cookie yang ditandatangani dengan secret key — user tidak bisa mengubahnya tanpa membatalkan tanda tangan. Helper current_user lalu membaca session:
def current_user
@current_user ||= User.find_by(id: session[:user_id])
endNote
Cookies dihasilkan dan didekripsi dengan secret key dari config/master.key (Rails Credentials, episode 16). Jika key hilang, semua session user menjadi tidak valid — simpan master.key di tempat aman (vault/secret manager), bukan di repo.
Rails 8 menghadirkan generator autentikasi resmi yang membuat seluruh flow — model, controller, session, dan view:
bin/rails g authentication
bin/rails db:migrateYang dihasilkan generator ini:
User dengan has_secure_password dan email_verification token.SessionsController, PasswordsController, RegistrationsController.login, logout, signup, plus reset password.ActiveJob + mailer.Ini adalah pilihan terbaik untuk app baru: default Rails yang aman, tanpa gem tambahan. Devise tetap relevan saat kalian butuh fitur lebih (konfirmasi, lockout, OmniAuth, admin engine).
Autentikasi hanya menjawab siapa; otorisasi menjawab boleh apa. Pundit adalah pendekatan populer berbasis policy objects:
bundle add pundit
bin/rails g pundit:installPolicy adalah class Ruby murni:
class PostPolicy < ApplicationPolicy
def index? = true
def show? = true
def create? = user.present?
def update? = record.user == user || user.admin?
def destroy? = record.user == user || user.admin?
endDipanggil dari controller:
def update
@post = Post.find(params[:id])
authorize @post
@post.update!(post_params)
redirect_to @post
endauthorize @post memanggil PostPolicy#update?; jika false, Pundit melempar Pundit::NotAuthorizedError (menjadi 403). Keuntungan policy object: aturan izin bisa diuji terpisah dan dipakai ulang di view.
Kombinasi roles + policies menciptakan RBAC sederhana. Tambahkan kolom role pada User:
bin/rails g migration AddRoleToUsers role:integer default:0class User < ApplicationRecord
enum :role, { user: 0, editor: 1, admin: 2 }
endSekarang user.admin?, user.editor!, dan user.role tersedia otomatis. Policy mengkombinasikan peran:
class PostPolicy < ApplicationPolicy
def publish?
user.admin? || user.editor?
end
endPola umum: halaman admin dibungkus di Admin:: namespace (episode 4) dengan filter require_admin di Admin::BaseController.
has_secure_password tanpa bcrypt di Gemfile — Rails sudah menyertakannya, tapi pastikan tidak dihapus; bcrypt wajib.authorize pada setiap action sensitif.user&.authenticate dihilangkan — pattern find_by lalu authenticate terpisah membuat timing attack user enumeration; pakai satu ekspresi dengan &..Episode 9 membekali kalian auth Rails: has_secure_password untuk hashing bcrypt, session berbasis cookie, generator Authentication Rails 8, Pundit dengan policy objects untuk authorization, dan enum role untuk RBAC.
Inti yang harus dibawa pulang:
has_secure_password menyimpan password_digest, bukan password mentah.session[:user_id] + helper current_user.authorize di controller.user/editor/admin) + policy = RBAC yang rapi.Di episode 10 selanjutnya kita akan membedah testing dengan RSpec/Minitest — unit test model, controller, system test dengan Capybara, fixtures vs factories, dan alur TDD. Sampai jumpa di episode 10!