Implementasi Autentikasi dalam Framework Golang Echo

Sebelumnya kita telah membahas bagaimana membangun suatu REST API pada golang menggunakan framework echo. Dalam artikel ini, kita akan membahas berbagai metode implementasi autentikasi dalam aplikasi framework Golang Echo, dengan fokus pada autentikasi JWT (JSON Web Tokens) dan implementasi middleware.

Mengapa Perlu Autentikasi?

Dalam pengembangan API, autentikasi diperlukan untuk:

  • Memverifikasi identitas pengguna yang mengakses API
  • Membatasi akses ke endpoint-endpoint tertentu
  • Melindungi data sensitif dari akses tidak sah
  • Mencatat aktivitas pengguna dalam sistem

Setup Dasar Autentikasi JWT

Pertama, mari kita siapkan dependensi yang diperlukan dan membuat struktur dasar untuk sistem autentikasi kita.

package main

import (
    "github.com/labstack/echo/v4"
    "github.com/golang-jwt/jwt/v5"
    "github.com/labstack/echo/v4/middleware"
    "time"
)

// User merepresentasikan model pengguna
type User struct {
    ID       uint   `json:"id"`
    Username string `json:"username"`
    Password string `json:"password"`
}

// LoginRequest merepresentasikan body permintaan login
type LoginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

// JWTClaim merepresentasikan klaim JWT
type JWTClaim struct {
    Username string `json:"username"`
    jwt.RegisteredClaims
}

Generasi Token JWT

Berikut cara mengimplementasikan generasi token setelah login berhasil:

const SECRET_KEY = "your-secret-key"

func generateToken(username string) (string, error) {
    claims := &JWTClaim{
        Username: username,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(SECRET_KEY))
}

func Login(c echo.Context) error {
    var loginRequest LoginRequest
    if err := c.Bind(&loginRequest); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "Payload permintaan tidak valid")
    }

    // Di sini Anda biasanya akan memvalidasi terhadap database
    // Ini hanya contoh
    if loginRequest.Username == "admin" && loginRequest.Password == "password" {
        token, err := generateToken(loginRequest.Username)
        if err != nil {
            return echo.NewHTTPError(http.StatusInternalServerError, "Gagal generate token")
        }
        return c.JSON(http.StatusOK, map[string]string{
            "token": token,
        })
    }

    return echo.NewHTTPError(http.StatusUnauthorized, "Kredensial tidak valid")
}

Implementasi Middleware JWT

Untuk melindungi route Anda, implementasikan middleware JWT:

func JWTMiddleware() echo.MiddlewareFunc {
    return middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey: []byte(SECRET_KEY),
        SigningMethod: "HS256",
        TokenLookup: "header:Authorization",
        AuthScheme: "Bearer",
        ParseTokenFunc: func(auth string, c echo.Context) (interface{}, error) {
            keyFunc := func(t *jwt.Token) (interface{}, error) {
                if t.Method.Alg() != "HS256" {
                    return nil, fmt.Errorf("metode signing jwt tidak terduga=%v", t.Header["alg"])
                }
                return []byte(SECRET_KEY), nil
            }

            token, err := jwt.ParseWithClaims(auth, &JWTClaim{}, keyFunc)
            if err != nil {
                return nil, err
            }
            if !token.Valid {
                return nil, errors.New("token tidak valid")
            }
            return token, nil
        },
    })
}

Implementasi Route yang Dilindungi

Berikut cara menyiapkan route yang dilindungi menggunakan middleware:

func main() {
    e := echo.New()

    // Route publik
    e.POST("/login", Login)

    // Grup route yang dilindungi
    r := e.Group("/api")
    r.Use(JWTMiddleware())

    // Endpoint yang dilindungi
    r.GET("/profile", GetProfile)
    r.PUT("/profile", UpdateProfile)

    e.Logger.Fatal(e.Start(":8080"))
}

func GetProfile(c echo.Context) error {
    user := c.Get("user").(*jwt.Token)
    claims := user.Claims.(*JWTClaim)

    return c.JSON(http.StatusOK, map[string]string{
        "username": claims.Username,
        "message": "Profil berhasil diakses",
    })
}

Praktik Terbaik dan Pertimbangan Keamanan

  1. Penyimpanan Kunci Rahasia yang Aman
  • Jangan pernah hardcode kunci rahasia Anda
  • Gunakan variabel lingkungan atau manajemen konfigurasi yang aman
   os.Getenv("JWT_SECRET_KEY")
  1. Kadaluwarsa Token
  • Selalu tetapkan waktu kadaluwarsa yang masuk akal
  • Implementasikan mekanisme refresh token untuk sesi yang lebih lama
  1. Penanganan Password
  • Selalu hash password sebelum menyimpan
   func hashPassword(password string) (string, error) {
       hashedPassword, err := bcrypt.GenerateFromPassword(
           []byte(password), 
           bcrypt.DefaultCost,
       )
       if err != nil {
           return "", err
       }
       return string(hashedPassword), nil
   }
  1. Penanganan Error
  • Berikan pesan error yang jelas tapi aman
  • Jangan ekspos informasi sensitif dalam error

Contoh Integrasi Database

Berikut contoh menggunakan GORM dengan PostgreSQL:

type UserModel struct {
    gorm.Model
    Username     string `gorm:"unique"`
    PasswordHash string
}

func AuthenticateUser(username, password string) (*UserModel, error) {
    var user UserModel
    result := db.Where("username = ?", username).First(&user)
    if result.Error != nil {
        return nil, result.Error
    }

    err := bcrypt.CompareHashAndPassword(
        []byte(user.PasswordHash), 
        []byte(password),
    )
    if err != nil {
        return nil, err
    }

    return &user, nil
}

Pengujian Autentikasi

Contoh pengujian endpoint autentikasi:

func TestLogin(t *testing.T) {
    e := echo.New()

    loginJSON := `{"username":"admin","password":"password"}`
    req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(loginJSON))
    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)

    if assert.NoError(t, Login(c)) {
        assert.Equal(t, http.StatusOK, rec.Code)
        assert.Contains(t, rec.Body.String(), "token")
    }
}

Kesimpulan

Implementasi autentikasi dalam framework Echo memerlukan pertimbangan yang cermat tentang praktik keamanan dan implementasi penanganan JWT yang tepat. Implementasi di atas memberikan fondasi yang solid untuk membangun API yang aman dengan Echo, tapi ingat untuk:

  • Menjaga keamanan kunci rahasia
  • Menjaga keamanan kunci rahasia Anda Mengimplementasikan hashing password yang tepat
  • Menggunakan HTTPS di production Secara rutin merotasi token Mengimplementasikan penanganan error yang tepat Menambahkan pembatasan rate untuk endpoint autentikasi