M
Mr Sugiarto
Developer
03 Sep 2026 5 min read

Sejauh ini sistem otorisasi kita cuma mengenal dua level: "belum login" dan "sudah login (dan pemilik resource)". Di aplikasi nyata, biasanya ada peran khusus (admin) yang boleh melakukan lebih banyak hal daripada user biasa. Di part ini kita tambahkan Role ke User, dan gunakan untuk membatasi beberapa aksi.

Menambahkan Kolom Role

Update models/user.go:

type User struct {
	gorm.Model
	Name     string `gorm:"size:255;not null" json:"name"`
	Email    string `gorm:"size:255;uniqueIndex;not null" json:"email"`
	Password string `gorm:"size:255;not null" json:"-"`
	Role     string `gorm:"size:20;not null;default:user" json:"role"`
}

default:user di tag GORM membuat database otomatis mengisi "user" kalau kolom ini tidak diisi eksplisit saat insert — baik untuk user baru yang register lewat endpoint biasa, maupun untuk user-user lama yang sudah ada sebelum migration ini (MySQL otomatis mengisi default itu ke baris yang sudah ada saat kolomnya ditambahkan).

Perhatikan: endpoint Register kita tidak pernah menerima role dari request — jadi tidak ada cara bagi user untuk mendaftar langsung sebagai admin. Admin pertama harus dibuat lewat jalur lain (nanti kita buat lewat seeder).

Membawa Role di JWT

Supaya middleware tidak perlu query database di setiap request hanya untuk tahu role user, kita sisipkan role langsung ke dalam JWT claims. Update auth/jwt.go:

type Claims struct {
	UserID uint   `json:"user_id"`
	Role   string `json:"role"`
	jwt.RegisteredClaims
}

func GenerateAccessToken(userID uint, role string) (string, error) {
	claims := Claims{
		UserID: userID,
		Role:   role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	// ...
}

Update pemanggilan GenerateAccessToken di Login:

accessToken, err := auth.GenerateAccessToken(user.ID, user.Role)

Di Refresh, role diambil ulang dari database (bukan dari refresh token lama) — supaya kalau role user berubah di antara waktu login dan refresh, access token yang baru selalu mencerminkan role terkini:

var user models.User
if err := h.DB.First(&user, stored.UserID).Error; err != nil {
	return response.Error(c, fiber.StatusInternalServerError, "gagal mengambil data user")
}

accessToken, err := auth.GenerateAccessToken(user.ID, user.Role)

Middleware AdminOnly

Update middleware/auth.go — simpan role ke c.Locals di AuthRequired, dan tambahkan middleware baru AdminOnly:

c.Locals("user_id", claims.UserID)
c.Locals("role", claims.Role)

return c.Next()
// AdminOnly harus dipasang setelah AuthRequired di rantai middleware -
// dia butuh c.Locals("role") yang cuma diisi kalau token sudah tervalidasi.
func AdminOnly(c *fiber.Ctx) error {
	role, _ := c.Locals("role").(string)
	if role != "admin" {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
			"message": "hanya admin yang boleh mengakses endpoint ini",
		})
	}

	return c.Next()
}

Karena AdminOnly bergantung pada c.Locals("role") yang diisi AuthRequired, urutan middleware di route penting — AuthRequired harus dipasang lebih dulu.

Kategori: Khusus Admin

Tambahkan Create dan Delete di CategoryHandler (lihat kode lengkap di repo), lalu daftarkan dengan kedua middleware berurutan:

api.Post("/categories", middleware.AuthRequired, middleware.AdminOnly, categoryHandler.Create)
api.Delete("/categories/:id", middleware.AuthRequired, middleware.AdminOnly, categoryHandler.Delete)

Admin Bisa Menghapus Post Siapa Saja

Update pengecekan kepemilikan di PostHandler.Delete — sebelumnya cuma mengizinkan pemilik post, sekarang admin juga diizinkan:

role, _ := c.Locals("role").(string)
if post.UserID != c.Locals("user_id").(uint) && role != "admin" {
	return response.Error(c, fiber.StatusForbidden, "anda tidak berhak menghapus post ini")
}

Update sengaja tidak diberi bypass yang sama — konten post tetap sepenuhnya milik penulisnya untuk diedit, admin cuma punya kewenangan moderasi (hapus), bukan menyunting isi tulisan orang lain. Ini pilihan desain, bukan keharusan; sesuaikan dengan kebutuhan aplikasi masing-masing.

Membuat Akun Admin Demo

Karena tidak ada endpoint publik untuk membuat admin, tambahkan ke cmd/seed/main.go:

// seedAdmin membuat akun admin demo (role=admin) - satu-satunya cara membuat
// admin di seri ini, karena endpoint register selalu memberi role "user".
func seedAdmin(db *gorm.DB) {
	var admin models.User
	if err := db.Where("email = ?", "admin@blogfiber.test").First(&admin).Error; err == nil {
		return
	}

	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("admin-password"), bcrypt.DefaultCost)

	admin = models.User{
		Name:     "Admin Blogfiber",
		Email:    "admin@blogfiber.test",
		Password: string(hashedPassword),
		Role:     "admin",
	}

	db.Create(&admin)
}

Testing

# Login sebagai user biasa dan admin
BUDI_TOKEN=...   # role: user
ADMIN_TOKEN=...  # role: admin, dari admin@blogfiber.test / admin-password

# User biasa coba buat kategori -> 403
curl -i -X POST http://127.0.0.1:3000/api/v1/categories \
  -H "Content-Type: application/json" -H "Authorization: Bearer $BUDI_TOKEN" \
  -d '{"name":"Hacked Category"}'

# Admin buat kategori -> 201
curl -X POST http://127.0.0.1:3000/api/v1/categories \
  -H "Content-Type: application/json" -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"name":"DevOps"}'

# User lain (bukan pemilik, bukan admin) coba hapus post Siti -> 403
curl -i -X DELETE http://127.0.0.1:3000/api/v1/posts/31 -H "Authorization: Bearer $BUDI_TOKEN"

# Admin hapus post Siti walau bukan pemiliknya -> 204
curl -i -X DELETE http://127.0.0.1:3000/api/v1/posts/31 -H "Authorization: Bearer $ADMIN_TOKEN"

Sistem permission kita sekarang punya dua lapis: kepemilikan (siapa yang boleh mengubah post-nya sendiri) dan role (apa yang admin boleh lakukan di luar itu). Di part berikutnya kita tulis automated test supaya semua alur yang sudah kita bangun bisa diverifikasi otomatis tanpa perlu curl manual satu-satu.

M
Mr Sugiarto

Developer

Bagian dari Series: GoFiber Dasar - Belajar Fundamental Lewat REST API Blog

Belajar konsep-konsep dasar GoFiber (routing, handler, GORM, MySQL, JWT auth) dengan membangun REST API blog sederhana dari nol - lengkap dengan auten...

Lihat Series Lengkap