Skip to content

Latest commit

 

History

History
273 lines (210 loc) · 5.88 KB

File metadata and controls

273 lines (210 loc) · 5.88 KB

Token

Membuat token auth menggunakan jwt token.

  • Tambahkan TOKEN_SALT environtment pada file .env
APP_PORT=9000
APP_ENV=local

DB_DRIVER=mysql
DB_SOURCE=root:pass@tcp(localhost:3306)/go-services?parseTime=true

TOKEN_SALT=secret-salt
  • Membuat token library pada libraries/token/token.go
package token

import (
    "os"
    "time"

    jwt "github.com/dgrijalva/jwt-go"
)

// MyCustomClaims struct
type MyCustomClaims struct {
    Username string `json:"username"`
    jwt.StandardClaims
}

var mySigningKey = []byte(os.Getenv("TOKEN_SALT"))

// ValidateToken for check token validation
func ValidateToken(myToken string) (bool, string) {
    token, err := jwt.ParseWithClaims(myToken, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return []byte(mySigningKey), nil
    })

    if err != nil {
        return false, ""
    }

    claims := token.Claims.(*MyCustomClaims)
    return token.Valid, claims.Username
}

// ClaimToken function
func ClaimToken(username string) (string, error) {
    claims := MyCustomClaims{
        username,
        jwt.StandardClaims{
            ExpiresAt: time.Now().Add(time.Hour * 5).Unix(),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // Sign the token with our secret
    return token.SignedString(mySigningKey)
}
  • Tambahkan login routing pada routing/route.go
    // Auth Routing
    {
        auth := controllers.Auths{Db: db, Log: log}
        app.Handle(http.MethodPost, "/login", auth.Login)
    }
  • Buat controllers auths pada controllers/auths.go
package controllers

import (
    "database/sql"
    "essentials/libraries/api"
    "essentials/libraries/token"
    "essentials/models"
    "essentials/payloads/request"
    "essentials/payloads/response"
    "fmt"
    "log"
    "net/http"

    "golang.org/x/crypto/bcrypt"
)

// Auths struct
type Auths struct {
    Db  *sql.DB
    Log *log.Logger
}

// Login http handler
func (u *Auths) Login(w http.ResponseWriter, r *http.Request) {
    var loginRequest request.LoginRequest
    err := api.Decode(r, &loginRequest, true)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, err)
        return
    }

    uLogin := models.User{Username: loginRequest.Username}
    err = uLogin.GetByUsername(r.Context(), u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("call login: %v", err))
        return
    }

    err = bcrypt.CompareHashAndPassword([]byte(uLogin.Password), []byte(loginRequest.Password))
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, api.ErrBadRequest(fmt.Errorf("compare password: %v", err), ""))
        return
    }

    token, err := token.ClaimToken(uLogin.Username)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("claim token: %v", err))
        return
    }

    var response response.TokenResponse
    response.Token = token

    api.ResponseOK(w, response, http.StatusOK)
}
  • Buat payload login request pada payloads/request/login_request.go
package request

//LoginRequest : format json request for login
type LoginRequest struct {
    Username string `json:"username"  validate:"required"`
    Password string `json:"password"  validate:"required"`
}
  • Buat GetByUsername method pada models/user.go
// GetByUsername : get user by username
func (u *User) GetByUsername(ctx context.Context, db *sql.DB) error {
    const q string = `SELECT id, username, password, email, is_active FROM users`
    err := db.QueryRowContext(ctx, q+" WHERE username=?", u.Username).Scan(&u.ID, &u.Username, &u.Password, &u.Email, &u.IsActive)

    if err == sql.ErrNoRows {
        err = api.ErrNotFound(err, "")
    }

    return err
}
  • Buat payload token response pada payloads/response/token_response.go
package response

//TokenResponse : format json response for token
type TokenResponse struct {
    Token string `json:"token"`
}
  • Buat api test untuk login. Buat file controllers/tests/authstest.go
package tests

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

// Auths : struct for set AUths Dependency Injection
type Auths struct {
    App   http.Handler
    Token string
}

// Login : http handler for login
func (u *Auths) Login(t *testing.T) {
    jsonBody := `
        {
            "username": "jackyhtg", 
            "password": "12345678"
        }
    `
    body := strings.NewReader(jsonBody)

    req := httptest.NewRequest("POST", "/login", body)
    req.Header.Set("Content-Type", "application/json")
    resp := httptest.NewRecorder()

    u.App.ServeHTTP(resp, req)

    if resp.Code != http.StatusOK {
        t.Fatalf("getting: expected status code %v, got %v", http.StatusOK, resp.Code)
    }

    var list map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
        t.Fatalf("decoding: %s", err)
    }

    u.Token = list["data"].(map[string]interface{})["token"].(string)

}
  • Update tests/main_test.go untuk mengetes login
package tests

import (
    apiTest "essentials/controllers/tests"
    "essentials/routing"
    "essentials/schema"
    "log"
    "os"
    "testing"

    _ "github.com/go-sql-driver/mysql"
)

var token string

func TestMain(t *testing.T) {

    db, teardown := NewUnit(t)
    defer teardown()

    if err := schema.Seed(db); err != nil {
        t.Fatal(err)
    }

    log := log.New(os.Stderr, "TEST : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)

    // api test for auths
    {
        auths := apiTest.Auths{App: routing.API(db, log)}
        t.Run("ApiLogin", auths.Login)
        token = auths.Token
    }

    // api test for users
    {
        users := apiTest.Users{App: routing.API(db, log)}
        t.Run("APiUsersList", users.List)
        t.Run("APiUsersCrud", users.Crud)
    }
}