Selamat datang kembali! Di bagian sebelumnya, kita telah berhasil membangun sistem registrasi dan login yang lengkap. Pengguna bisa masuk, dan aplikasi kita “mengingat” mereka melalui session cookie.
Sekarang, apa gunanya login jika semua halaman bisa diakses oleh siapa saja? Di bagian ini, kita akan membuat sistem autentikasi kita benar-benar berguna dengan:
- Membuat Middleware Otentikasi: Sebuah “penjaga gerbang” yang akan melindungi rute-rute sensitif.
- Mengelola Konteks Request: Menyimpan informasi pengguna yang login agar bisa diakses oleh handler lain.
- Membuat Halaman Akun Pengguna: Halaman sederhana yang hanya bisa diakses oleh pengguna yang sudah login.
- Mengimplementasikan Logout.
Mari kita amankan aplikasi kita!
Langkah 1: Membuat Middleware Otentikasi
Middleware di chi
adalah sebuah fungsi yang “membungkus” http.Handler
. Ia bisa melakukan sesuatu sebelum atau sesudah handler utama dieksekusi. Kita akan membuat middleware yang memeriksa sesi sebelum meneruskan request ke handler tujuan.
Buat direktori dan file baru untuk middleware kita:
mkdir -p cmd/web/middleware
touch cmd/web/middleware/auth.go
Isi file cmd/web/middleware/auth.go
dengan kode berikut:
// cmd/web/middleware/auth.go
package middleware
import (
"context"
"net/http"
"github.com/kodekilat/go-ecommerce/cmd/web/session" // Ganti path modul Anda
)
// Definisikan kunci unik untuk konteks
type contextKey string
const userContextKey = contextKey("user")
// Authenticate adalah middleware yang memeriksa sesi pengguna
func Authenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Dapatkan sesi dari request
sess, _ := session.Store.Get(r, "auth-session")
// 2. Periksa apakah user_id ada di sesi
userID, ok := sess.Values["user_id"]
if !ok || userID == nil {
// Jika tidak ada, panggil handler berikutnya tanpa melakukan apa-apa
// (artinya pengguna adalah tamu/guest)
next.ServeHTTP(w, r)
return
}
// 3. Jika user_id ada, simpan di konteks request
ctx := context.WithValue(r.Context(), userContextKey, userID)
// 4. Panggil handler berikutnya dengan request yang sudah dimodifikasi konteksnya
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Penjelasan:
contextKey
: Membuat tipe kunci sendiri adalah praktik terbaik untuk menghindari tabrakan kunci di konteks.context.WithValue
: Kita menggunakan konteks request untuk “membawa”user_id
ke handler-handler selanjutnya. Ini adalah cara modern dan aman untuk berbagi data per-request.- Middleware ini tidak memblokir request. Ia hanya menambahkan
user_id
ke konteks jika pengguna sudah login.
Langkah 2: Menerapkan Middleware di Router
Sekarang, kita terapkan middleware ini ke semua rute kita. Buka cmd/web/router/router.go
dan tambahkan r.Use(authMiddleware.Authenticate)
.
// cmd/web/router/router.go
import (
// ...
authMiddleware "github.com/kodekilat/go-ecommerce/cmd/web/middleware" // Import paket middleware
)
func New(userRepo *repository.UserRepository) http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(authMiddleware.Authenticate) // Terapkan middleware otentikasi ke semua rute
// ... (sisa kode)
}
Langkah 3: Membuat Middleware “Require Authentication”
Middleware Authenticate
hanya menambahkan data ke konteks. Sekarang kita butuh middleware kedua yang benar-benar memblokir akses jika pengguna belum login.
Kembali ke cmd/web/middleware/auth.go
dan tambahkan fungsi baru:
// cmd/web/middleware/auth.go
// ...
// RequireAuthentication adalah middleware yang menolak akses jika pengguna belum login
func RequireAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Ambil user_id dari konteks
userID := r.Context().Value(userContextKey)
// Jika tidak ada user_id (pengguna adalah tamu), redirect ke halaman login
if userID == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Jika ada, lanjutkan ke handler tujuan
next.ServeHTTP(w, r)
})
}
Langkah 4: Melindungi Halaman Akun
Mari kita buat halaman “Akun Saya” yang hanya bisa diakses setelah login.
1. Buat Template & Handler
Buat template web/templates/account.page.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Akun Saya</title>
<!-- Gunakan style yang sama -->
</head>
<body>
<div class="container">
<h2>Selamat Datang, {{.Email}}!</h2>
<p>Ini adalah halaman akun Anda yang terlindungi.</p>
<p>User ID Anda: {{.UserID}}</p>
<br>
<form action="/logout" method="POST">
<button type="submit">Logout</button>
</form>
</div>
</body>
</html>
Buat handler baru di cmd/web/handler/user.go
(file baru):
// cmd/web/handler/user.go
package handler
import (
"net/http"
"github.com/kodekilat/go-ecommerce/cmd/web/session"
"github.com/kodekilat/go-ecommerce/cmd/web/view"
)
// Definisikan kunci konteks yang sama
type contextKey string
const userContextKey = contextKey("user")
func ShowAccountPage(w http.ResponseWriter, r *http.Request) {
// Ambil data dari sesi (cara alternatif selain dari konteks)
sess, _ := session.Store.Get(r, "auth-session")
userID := sess.Values["user_id"]
email := sess.Values["user_email"]
pageData := struct {
UserID interface{}
Email interface{}
}{
UserID: userID,
Email: email,
}
view.Render(w, "account.page.html", pageData)
}
2. Terapkan Middleware Pelindung di Router
Buka cmd/web/router/router.go
. Kita akan membuat grup rute baru yang dilindungi oleh RequireAuthentication
.
// cmd/web/router/router.go
// ... (imports)
import "github.com/kodekilat/go-ecommerce/cmd/web/handler" // Pastikan ada import user handler
// ...
func New(userRepo *repository.UserRepository) http.Handler {
// ... (kode sebelumnya)
// Rute publik (bisa diakses siapa saja)
r.Get("/", handler.ShowHomePage)
// ...
// Grup rute yang memerlukan autentikasi
r.Group(func(r chi.Router) {
r.Use(authMiddleware.RequireAuthentication)
// Daftarkan rute yang dilindungi di sini
r.Get("/account", handler.ShowAccountPage)
})
return r
}
Sekarang, coba akses http://localhost:8080/account
. Jika Anda belum login, Anda akan otomatis dialihkan ke /login
. Jika sudah login, Anda akan melihat halaman akun Anda!
Langkah 5: Implementasi Logout
Logout pada dasarnya adalah proses menghancurkan sesi.
1. Buat Handler Logout
Tambahkan method HandleLogout
ke cmd/web/handler/auth.go
:
// cmd/web/handler/auth.go
// ...
func (h *AuthHandler) HandleLogout(w http.ResponseWriter, r *http.Request) {
sess, _ := session.Store.Get(r, "auth-session")
// Hapus sesi dengan mengatur MaxAge menjadi -1
sess.Options.MaxAge = -1
sess.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
2. Daftarkan Rute Logout
Di cmd/web/router/router.go
, tambahkan rute untuk logout. Ini bisa menjadi rute publik.
// cmd/web/router/router.go
// ...
func New(...) http.Handler {
// ...
authHandler := &handler.AuthHandler{UserRepo: userRepo}
// ... (rute-rute lain)
r.Post("/logout", authHandler.HandleLogout) // Tambahkan rute ini
// ... (grup rute terproteksi)
return r
}
Sekarang, tombol Logout di halaman akun Anda akan berfungsi!
Penutup
Kerja yang sangat bagus! Kita telah membangun pilar penting dari aplikasi web modern: middleware untuk otentikasi dan otorisasi. Aplikasi kita kini bisa membedakan antara pengguna tamu dan pengguna yang sudah login, serta melindungi konten yang sesuai.
Di bagian selanjutnya, kita akan kembali ke inti dari e-commerce: Manajemen Produk. Kita akan membangun fungsionalitas CRUD (Create, Read, Update, Delete) untuk produk dan mengintegrasikannya dengan Minio untuk upload gambar.
Anda sudah sangat jauh melangkah. Teruslah coding!
Untuk referensi, Anda dapat melihat kode final dari tutorial ini di repositori GitHub berikut: Source code lengkap