Di part ini kita akan menambahkan sistem autentikasi sederhana ke aplikasi blog kita: endpoint Register dan Login, sekaligus generate JWT Token yang nantinya dipakai untuk mengakses endpoint yang butuh proteksi (akan kita bahas di part selanjutnya).
Instalasi Package
Kita akan menggunakan library golang-jwt untuk membuat dan memvalidasi token, serta bcrypt untuk hashing password:
go get github.com/golang-jwt/jwt/v5
go get golang.org/x/crypto/bcrypt
Membuat Model User
package models
import (
"github.com/goravel/framework/database/orm"
)
type User struct {
orm.Model
Name string
Email string
Password string
}
Membuat Helper JWT
Buat file baru app/helpers/jwt_helper.go untuk generate dan validasi token:
package helpers
import (
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
func getJWTSecret() []byte {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
secret = "your-secret-key"
}
return []byte(secret)
}
type JWTClaim struct {
UserID string `json:"user_id"`
jwt.RegisteredClaims
}
func GenerateJWT(userID string) (string, error) {
expirationTime := time.Now().Add(10 * time.Minute)
claims := &JWTClaim{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(getJWTSecret())
}
func ValidateJWT(tokenString string) (*JWTClaim, error) {
token, err := jwt.ParseWithClaims(tokenString, &JWTClaim{}, func(token *jwt.Token) (interface{}, error) {
return getJWTSecret(), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*JWTClaim); ok && token.Valid {
return claims, nil
}
return nil, err
}
Token yang kita generate di sini masa berlakunya (expirationTime) di-hardcode 10 menit. Untuk kebutuhan production, sebaiknya nilai ini diambil dari config/env, bukan hardcode di kode program.
Jangan lupa tambahkan JWT_SECRET di file .env:
JWT_SECRET=isi-dengan-string-rahasia-kamu
Menambahkan Method Register dan Login
Lengkapi app/http/controllers/user_controller.go dengan method Register:
func (r *UserController) Register(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|max_len:100",
"email": "required|email",
"password": "required|min_len:6",
})
if err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": err.Error(),
})
}
if validator.Fails() {
return ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
"error": validator.Errors().All(),
})
}
var existingUser models.User
email := ctx.Request().Input("email")
err = facades.Orm().Query().Where("email", email).First(&existingUser)
if err == nil && existingUser.ID > 0 {
return ctx.Response().Json(http.StatusConflict, http.Json{
"email": "Email already registered",
})
}
password := ctx.Request().Input("password")
hashed, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": "Failed to hash password",
})
}
user := models.User{
Name: ctx.Request().Input("name"),
Email: ctx.Request().Input("email"),
Password: string(hashed),
}
if err := facades.Orm().Query().Create(&user); err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": err.Error(),
})
}
token, err := helpers.GenerateJWT(fmt.Sprint(user.ID))
if err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": "Failed to generate token",
})
}
return ctx.Response().Json(http.StatusOK, http.Json{
"token": token,
"user": http.Json{
"id": user.ID,
"name": user.Name,
"email": user.Email,
},
})
}
Beberapa hal penting di method Register ini:
- Validasi input
name,email, danpassword. - Cek apakah email sudah terdaftar sebelumnya — kalau sudah, response
409 Conflict. - Password di-hash menggunakan
bcryptsebelum disimpan — jangan pernah simpan password dalam bentuk plain text. - Setelah user berhasil dibuat, langsung generate JWT token supaya user tidak perlu login ulang setelah register.
Lanjut method Login:
func (r *UserController) Login(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"email": "required|email",
"password": "required|min_len:6",
})
if err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": err.Error(),
})
}
if validator.Fails() {
return ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
"error": validator.Errors().All(),
})
}
email := ctx.Request().Input("email")
var user models.User
if err := facades.Orm().Query().Where("email", email).First(&user); err != nil {
return ctx.Response().Json(http.StatusUnauthorized, http.Json{
"error": "Invalid credentials",
})
}
if user.ID == 0 {
return ctx.Response().Json(http.StatusUnauthorized, http.Json{
"error": "Invalid credentials",
})
}
password := ctx.Request().Input("password")
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return ctx.Response().Json(http.StatusUnauthorized, http.Json{
"error": "Invalid credentials",
})
}
token, err := helpers.GenerateJWT(fmt.Sprint(user.ID))
if err != nil {
return ctx.Response().Json(http.StatusInternalServerError, http.Json{
"error": "Failed to generate token",
})
}
return ctx.Response().Json(http.StatusOK, http.Json{
"token": token,
"user": http.Json{
"id": user.ID,
"name": user.Name,
"email": user.Email,
},
})
}
Di method Login, password yang dikirim user dibandingkan dengan hash yang tersimpan menggunakan bcrypt.CompareHashAndPassword. Kalau cocok, baru di-generate JWT token yang baru.
Menambahkan Route
userController := controllers.NewUserController()
facades.Route().Post("/register", userController.Register)
facades.Route().Post("/login", userController.Login)
facades.Route().Get("/users/{id}", userController.Show)
Testing Endpoint
Coba endpoint berikut menggunakan Postman:
POST /registerdengan body{"name": "...", "email": "...", "password": "..."}POST /logindengan body{"email": "...", "password": "..."}
Kalau berhasil, response akan berisi token — token inilah yang nanti kita pakai untuk mengakses endpoint yang butuh autentikasi di part selanjutnya.
Video Tutorial
Tutorial dalam bentuk video dapat di akses di channel Youtube IndoKoding: https://www.youtube.com/watch?v=s7VZku47dUo
Bagian dari Series: Seri Tutorial Belajar Framework Goravel Rest API untuk Pemula
Overview Seri ini membahas cara membangun REST API menggunakan Goravel, framework web berbasis Golang yang terinspirasi dari Laravel. Materi mencakup...
Lihat Series Lengkap