M
Mr Sugiarto
Developer
20 Aug 2026 5 min read

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:

  1. Validasi input name, email, dan password.
  2. Cek apakah email sudah terdaftar sebelumnya — kalau sudah, response 409 Conflict.
  3. Password di-hash menggunakan bcrypt sebelum disimpan — jangan pernah simpan password dalam bentuk plain text.
  4. 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 /register dengan body {"name": "...", "email": "...", "password": "..."}
  • POST /login dengan 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

M
Mr Sugiarto

Developer

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