Episode ini melengkapi sisi keamanan: middleware RBAC dan permission, integrasi OAuth2 dan OpenID Connect dengan Keycloak, security headers bergaya helmet, perlindungan CSRF untuk form, serta rate limiting dengan golang.org/x/time/rate.

Autentikasi menjawab pertanyaan "siapa pengguna ini". Episode 14 ini menjawab pertanyaan berikutnya: apa yang boleh ia lakukan? Kalian akan membedah otorisasi berbasis peran (RBAC), integrasi OAuth2 dan OpenID Connect dengan Keycloak, serta kumpulan security middleware: CORS, security headers bergaya helmet, proteksi CSRF untuk form, dan rate limiting yang matang.
Kenapa penting? Mengizinkan semua pengguna melakukan semua operasi adalah resep bencana. Ditambah lagi, header yang salah atau CORS yang terlalu terbuka bisa membuka celah yang dieksploitasi dari browser. Middleware yang kalian pasang di sini menutup celah-celah itu satu per satu.
RBAC memetakan peran ke daftar permission:
type Permission string
const (
PermUserRead Permission = "user:read"
PermUserWrite Permission = "user:write"
)
var rolePermissions = map[string][]Permission{
"admin": {PermUserRead, PermUserWrite},
"editor": {PermUserRead},
}rolePermissions memetakan peran seperti admin dan editor ke daftar permission yang boleh dilakukan. Struktur ini mudah dibaca dan diperluas: menambah peran baru cukup menambah entri di map.
Middleware memeriksa peran pengguna lalu mencocokkan permission:
func requirePermission(perm Permission) gin.HandlerFunc {
return func(c *gin.Context) {
role := c.GetString("role")
allowed := false
for _, p := range rolePermissions[role] {
if p == perm {
allowed = true
break
}
}
if !allowed {
c.AbortWithStatusJSON(403, gin.H{"error": "akses ditolak"})
return
}
c.Next()
}
}
r.PUT("/users/:id", authMiddleware(), requirePermission(PermUserWrite), updateUser)c.GetString("role") mengambil peran yang disimpan middleware autentikasi dari episode 13. Jika pengguna tidak punya permission yang diminta, middleware mengembalikan 403 Forbidden. Perhatikan urutan: autentikasi dulu (siapa), baru otorisasi (boleh apa).
OAuth2 memindahkan otentikasi ke penyedia seperti Keycloak, Google, atau GitHub. Siapkan config lalu alihkan pengguna:
go get golang.org/x/oauth2oauthCfg := &oauth2.Config{
ClientID: "belajar-gin",
ClientSecret: "client-rahasia",
RedirectURL: "http://localhost:8080/auth/callback",
Endpoint: oauth2.Endpoint{
AuthURL: "https://keycloak.example.com/realms/dev/protocol/openid-connect/auth",
TokenURL: "https://keycloak.example.com/realms/dev/protocol/openid-connect/token",
},
Scopes: []string{"openid", "profile"},
}
r.GET("/auth/login", func(c *gin.Context) {
url := oauthCfg.AuthCodeURL("state-acak")
c.Redirect(302, url)
})oauthCfg.AuthCodeURL("state-acak") menghasilkan URL penyedia OAuth2 tempat pengguna login. Parameter state mencegah serangan CSRF pada alur OAuth — selalu gunakan nilai acak dan verifikasi di callback.
Browser menghormati sejumlah header yang mengurangi serangan:
func securityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("Referrer-Policy", "no-referrer")
c.Header("Content-Security-Policy", "default-src 'self'")
c.Next()
}
}c.Header("Content-Security-Policy", "default-src 'self'") membatasi sumber konten yang boleh dimuat halaman — pertahanan utama terhadap XSS. Kombinasi nosniff, X-Frame-Options: DENY, dan Referrer-Policy memangkas permukaan serangan dari browser. Untuk HTTPS, tambahkan Strict-Transport-Security seperti akan dibahas di episode 15.
Form web rentan terhadap request lintas situs. Proteksi paling umum adalah synchronizer token:
func setCSRF(c *gin.Context) {
token := csrfToken() // acak, disimpan di session
c.SetCookie("csrf_token", token, 3600, "/", "", true, true)
c.HTML(200, "form.html", gin.H{"csrf_token": token})
}c.SetCookie("csrf_token", token, 3600, "/", "", true, true) menulis token ke cookie dengan flag Secure dan HttpOnly. Saat form dikirim, c.Cookie("csrf_token") dibandingkan dengan token form memakai perbandingan waktu konstan. Situs jahat tidak bisa membaca cookie karena flag HttpOnly, sehingga tidak bisa memalsukan token. Untuk API yang memakai cookie session, ini wajib dipasang — kombinasi CSRF dan cookie tanpa proteksi sangat berbahaya.
Episode 12 membangun limiter per IP. Untuk versioning yang lebih matang, pakai limiter dengan fallback per IP dan key pemisah:
var (
limitMu sync.Mutex
limiters = make(map[string]*rate.Limiter)
interval = rate.Every(time.Minute)
)
func rateLimit() gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
limitMu.Lock()
lim, ok := limiters[ip]
if !ok {
lim = rate.NewLimiter(interval, 60)
limiters[ip] = lim
}
limitMu.Unlock()
if !lim.Allow() {
c.AbortWithStatusJSON(429, gin.H{"error": "terlalu banyak request"})
return
}
c.Next()
}
}rate.NewLimiter(interval, 60) membolehkan 60 request per menit per IP dengan burst 60. c.ClientIP() memakai engine trusted proxies — pastikan dikonfigurasi benar (episode 15) agar IP tidak mudah dipalsukan. Bersihkan map secara berkala agar limiter IP lama tidak menumpuk.
Inti yang harus dibawa pulang:
c.GetString("role").Exchange menukar code dengan token.nosniff memangkas serangan dari browser.HttpOnly mencegah request lintas situs.rate.Limiter membatasi request per IP dengan aman.Di episode 15 selanjutnya kita akan membedah HTTPS, trusted proxies & input hardening — mengonfigurasi trusted proxies dengan benar, menjalankan TLS, integrasi reverse proxy nginx dan caddy, serta pagination dan pencegahan injection serta SSRF.