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.
- Melakukan pengecekan apakah memiliki API tersebut memiliki header
X-API-Key
- Jika tidak ada, maka akan memberikan informasi error
Unauthorized
- 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
- Unauthorized ketika kita mengakses service tanpa mengirimkan Header
X-API-Key
dengan values3cr3t
- Sukses mengakses service dengan mengirimkan Header yang sesuai
- Unauthorized ketika kita mengirimkan Header
X-API-Key
dengan value yang salah misalkansecret
.
Berikut Hasil dari pengujian kita coba
Pengujian tanpa headerX-API-Key
terjadi errorPengujian sukses dengan header X-API-Key
dengan value yang sesuaiPengujian dengan header key yang salah