pemrograman

10 Menambahkan Authentication Sederhana

Pada tahapan ini kita akan mencoba menambahkan Authentication sederhana dengan menggunakan middleware pada Golang. Perlu teman-teman ketahui bahwa middleware adalah proses mencegat suatu API yang mana sebelum service ketika diakses menuju ke dalam handler layer maka akan melewati middleware layer ini (yang akan kita buat) untuk menangkap dan memproses sesuatu untuk kebutuhan tertentu. Misalkan, pada kasus ini kita akan mencekat proses service untuk melihat apakah API tersebut memiliki header dengan ketentuan key X-API-Key.

Maka, middleware yang akan kita buat ini memiliki aturan sebagai berikut ini.

  1. Melakukan pengecekan apakah memiliki API tersebut memiliki header X-API-Key
  2. Jika tidak ada, maka akan memberikan informasi error Unauthorized
  3. Dan jika ada dan sesuai dengan yang sudah ditentukan valuenya yaitu s3cr3t dan sama, maka proses akan dilanjutkan.

Berikut kurang lebih flow middleware yang akan kita buat.

---
title: Authientication Middleware
---
stateDiagram-v2
    [*] --> hasHeader
    hasHeader --> unAuthorized
    unAuthorized --> [*]

    hasHeader --> HeaderXAPIKey
    HeaderXAPIKey --> unAuthorized

    HeaderXAPIKey --> SuccessContinue
    SuccessContinue --> [*]

Pembuatan Chain Middleware

Pertama kita akan buat folder pkg/middleware-chain dengan nama middleware_chain.go. Berikut ini isi dari file tersebut.

package middleware_chain

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
)

// Constructor is type of httprouter handler
type Constructor func(httprouter.Handle) httprouter.Handle

// Chain is struck for list of middleware
type Chain struct {
	constructors []Constructor
}

// New is for innitial new chain of
func New(constructors ...Constructor) Chain {
	return Chain{append(([]Constructor)(nil), constructors...)}
}

// Then is for http router handler
func (c Chain) Then(h httprouter.Handle) httprouter.Handle {
	if h == nil {
		return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {}
	}
	for i := range c.constructors {
		h = c.constructors[len(c.constructors)-1-i](h)
	}

	return h
}

// Append is for add chain router handler
func (c Chain) Append(constructors ...Constructor) Chain {
	newCons := make([]Constructor, 0, len(c.constructors)+len(constructors))
	newCons = append(newCons, c.constructors...)
	newCons = append(newCons, constructors...)

	return Chain{newCons}
}

Pada middleware yang sudah kita buat ini digunakan untuk general handling yang mana ketika nanti kita memiliki middleware yang lebih dari satu maka tidak perlu melakukan extend lebih sehingga mengakibatkan router yang cukup panjang pada penamaannya tetapi kita hanya cukup menambahkan saat inisialisasi router dengan mudah seperti ini.

	// inisialisasi http router
	router := httprouter.New()

	// inisialisasi chain middleware
	m := middleware_chain.New(
		// fungsi middleware yang akan kita tambahkan
	)

Membuat Middleware Authentication Sederhana

Pada fungsi yang akan kita buat ini yaitu middleware yang kebutuhannya untuk melakukan Authentication service agar tidak sembarangan orang mengakses service dan data kita sehingga lebih aman.

Sesuai dengan yang sudah dijelaskan diatas proses authentication middleware ini akan kita buat untuk service kita yang mana file-nya akan kita simpan di folder middleware/auth.go dan isi file tersebut dengan kode dibawah ini.

func AuthenticationBasic(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		if r.Header.Get(XApiKey) != Secret {
			var statusCode = http.StatusUnauthorized
			var response models.HeaderResponse
			response.Code = statusCode
			response.Status = "Unauthorized"
			util.Response(w, response, statusCode)
			return
		}

		next(w, r, params)
	}
}

Sangat simpel karena kita ingin mencoba membuat middleware yang sederhana dan mudah terlebih dahulu agar teman-teman bisa paham proses dari middleware ini.

Mengubah Inisialisasi Router menjadi Fungsi Sendiri

Pada tahapan ini kita akan memisahkan inisialisasi Router API menjadi satu fungsi tersendiri agar mudah mengenal dan tidak terlalu panjang pada saat memiliki endpoint yang banyak mengakibatkan file main.go menjadi panjang dan besar. Sehingga akan kita separate kode inisialisasi Router API dengan kode seperti dibawah ini.

func NewRouter(articleHandler *httpHandler.Delivery) *httprouter.Router {
	// inisialisasi http router
	router := httprouter.New()

	// inisialisasi chain middleware
	m := middleware_chain.New(
		middleware.AuthenticationBasic,
	)

	// entrypoint
	router.GET("/api/articles", m.Then(articleHandler.GetAll))
	router.GET("/api/articles/:article_id", m.Then(articleHandler.GetByID))
	router.POST("/api/articles/", m.Then(articleHandler.Store))
	router.PUT("/api/articles/:article_id", m.Then(articleHandler.Update))
	router.DELETE("/api/articles/:article_id", m.Then(articleHandler.Delete))

	return router
}

Dan jangan lupa kita ubah inisialisasi Router API pada file main.go seperti ini.

func main() {
	fileEnv := ".env"
	if os.Getenv("environment") == "development" {
		fileEnv = "../.env"
	}

	err := godotenv.Load(fileEnv)
	if err != nil {
		log.Fatalf("error loading .env file")
	}

	// inisialisasi database
	db := database.New()

	// inisialisasi repository
	repository := mysqlRepository.New(db)
	// inisialisasi usecase
	articleUsecase := articleUsecase.New(repository)
	// inisialisasi handler
	articleHandler := httpHandler.New(articleUsecase)
	// inisialisasi new router
	router := NewRouter(articleHandler)

	server := http.Server{
		Addr:    "localhost:3000",
		Handler: router,
	}

	err = server.ListenAndServe()
	if err != nil {
		panic(err)
	}
}

Pengujian

Setelah kita memasang Authentication Middleware kita perlu mencobanya di setiap endpoint yang sudah kita buat. Pengujiannya itu berarti ada dua skenario yaitu

  1. Unauthorized ketika kita mengakses service tanpa mengirimkan Header X-API-Key dengan value s3cr3t
  2. Sukses mengakses service dengan mengirimkan Header yang sesuai
  3. Unauthorized ketika kita mengirimkan Header X-API-Key dengan value yang salah misalkan secret.

Berikut Hasil dari pengujian kita coba

without header api key

Pengujian tanpa header X-API-Key terjadi error

with header api key success

Pengujian sukses dengan header X-API-Key dengan value yang sesuai

wrong header api key

Pengujian dengan header key yang salah

comments powered by Disqus