11 Membuat Middleware Basic Authentication HTTP Router pada Golang
Basic Authentication adalah metode autentikasi sederhana di mana klien mengirimkan kredensial (username dan password) dalam header setiap permintaan HTTP. Pada artikel ini, kita akan mempelajari cara membuat middleware Basic Authentication di Golang menggunakan library httprouter .
Artikel ini ditujukan untuk programmer pemula dengan langkah-langkah detail dan penjelasan untuk mempermudah pemahaman.
Prasyarat
- Pemahaman dasar tentang Golang: Memahami fungsi dasar, struktur, dan cara kerja package.
- Golang terinstal: Pastikan Anda telah menginstal Go di komputer.
- Library pendukung: Instal library
github.com/julienschmidt/httprouterdengan perintah berikut:1go get github.com/julienschmidt/httprouter
1. Struktur Proyek
Buat struktur proyek seperti berikut:
1project-root/
2├── main.go
3├── handlers.go
4├── pkg/
5│ ├── middleware/
6│ │ └── auth.go
7│ └── utils/
8│ └── jwt.goFile main.go adalah entry point aplikasi, sedangkan middleware/ dan handlers/ digunakan untuk memisahkan middleware dan fungsi endpoint.
2. Membuat File Utama
Buka file main.go dan tambahkan kode berikut:
1package main
2
3import (
4 "fmt"
5 "log"
6 "net/http"
7 "github.com/julienschmidt/httprouter"
8 "project-root/middleware"
9 "project-root/handlers"
10)
11
12func main() {
13 router := httprouter.New()
14
15 // sample testing generate basic authentication
16 fmt.Println("generate basic auth: " + base64.StdEncoding.EncodeToString([]byte("ihsan:password")))
17
18 // Endpoint publik tanpa autentikasi
19 router.GET("/public", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
20 fmt.Fprint(w, "Endpoint publik tidak membutuhkan autentikasi!\n")
21 })
22
23 // Endpoint privat menggunakan middleware Basic Authentication
24 router.GET("/private", middleware.BasicAuth(handlers.PrivateHandler))
25
26 log.Println("Server berjalan di http://localhost:8080")
27 log.Fatal(http.ListenAndServe(":8080", router))
28}Kode di atas mendefinisikan endpoint publik dan endpoint privat yang dilindungi oleh middleware Basic Authentication.
3. Membuat Middleware Basic Authentication
3.1. Membuat Fungsi Middleware Basic Authentication
Buka file middleware/basic_auth.go dan tuliskan kode berikut:
1package middleware
2
3import (
4 "encoding/base64"
5 "net/http"
6 "strings"
7 "github.com/julienschmidt/httprouter"
8)
9
10var validUsers = map[string]string{
11 "admin": "password123",
12 "user": "password456",
13}
14
15func BasicAuth(next httprouter.Handle) httprouter.Handle {
16 return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
17 authHeader := r.Header.Get("Authorization")
18 if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") {
19 http.Error(w, "Unauthorized", http.StatusUnauthorized)
20 return
21 }
22
23 // Decode Base64 dari kredensial
24 payload, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic "))
25 if err != nil {
26 http.Error(w, "Invalid authentication token", http.StatusUnauthorized)
27 return
28 }
29
30 cred := strings.SplitN(string(payload), ":", 2)
31 if len(cred) != 2 || !validateUser(cred[0], cred[1]) {
32 http.Error(w, "Invalid username or password", http.StatusUnauthorized)
33 return
34 }
35
36 // Lanjutkan ke handler berikutnya jika valid
37 next(w, r, ps)
38 }
39}
40
41func validateUser(username, password string) bool {
42 if pass, ok := validUsers[username]; ok {
43 return pass == password
44 }
45 return false
46}Middleware ini membaca header Authorization, men-decode kredensial yang dienkripsi dalam Base64, dan memvalidasi username serta password menggunakan data yang disimpan di memori (dalam map validUsers).
3.2. Menambahkan pengujian fungsi dengan unit test
Berikut pengujian menggunakan unit test functionality dibawah ini.
1package midleware
2
3import (
4 "encoding/base64"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10
11 "github.com/julienschmidt/httprouter"
12)
13
14func TestBasicAuth(t *testing.T) {
15 type args struct {
16 username string
17 password string
18 }
19 tests := []struct {
20 name string
21 args args
22 mock func(args args) string
23 wantHTTPStatus int
24 wantResponse string
25 }{
26 {
27 name: "success valid basic authentication",
28 args: args{
29 username: "ihsan",
30 password: "password",
31 },
32 mock: func(args args) string {
33 return "Basic " + base64.StdEncoding.EncodeToString([]byte(args.username+":"+args.password))
34 },
35 wantHTTPStatus: http.StatusOK,
36 wantResponse: fmt.Sprintln("Selamat datang di endpoint private menggunakan basic authentication! Anda berhasil terautentikasi."),
37 },
38 {
39 name: "invalid because authorization header not set",
40 args: args{},
41 mock: func(args args) string {
42 return ""
43 },
44 wantHTTPStatus: http.StatusUnauthorized,
45 wantResponse: fmt.Sprintln("Unauthorized"),
46 },
47 {
48 name: "invalid username or password",
49 args: args{
50 username: "santekno",
51 password: "password",
52 },
53 mock: func(args args) string {
54 return "Basic " + base64.StdEncoding.EncodeToString([]byte(args.username+":"+args.password))
55 },
56 wantHTTPStatus: http.StatusUnauthorized,
57 wantResponse: fmt.Sprintln("Invalid username or password"),
58 },
59 }
60 for _, tt := range tests {
61 t.Run(tt.name, func(t *testing.T) {
62 token := tt.mock(tt.args)
63
64 // membuat request dengan header yang valid
65 request := httptest.NewRequest(http.MethodGet, "/", nil)
66 request.Header.Set("Authorization", token)
67
68 // buat recorder untuk menangkap response
69 recorder := httptest.NewRecorder()
70
71 handler := BasicAuth(MockBasicAuthHandler)
72 handler(recorder, request, httprouter.Params{})
73
74 if recorder.Code != tt.wantHTTPStatus {
75 t.Errorf("expected 200 ok, got %d", recorder.Code)
76 }
77
78 if !strings.Contains(recorder.Body.String(), tt.wantResponse) {
79 t.Errorf("unexpected response body %s", recorder.Body.String())
80 }
81 })
82 }
83}
84
85func MockBasicAuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
86 fmt.Fprint(w, "Selamat datang di endpoint private menggunakan basic authentication! Anda berhasil terautentikasi.\n")
87}4. Membuat Handler untuk Endpoint Privat
Buka file handlers/user.go dan tambahkan kode berikut:
1package handlers
2
3import (
4 "fmt"
5 "net/http"
6 "github.com/julienschmidt/httprouter"
7)
8
9func PrivateHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
10 fmt.Fprint(w, "Selamat datang di endpoint privat! Anda berhasil terautentikasi.\n")
11}Handler ini hanya akan diakses jika kredensial Basic Authentication yang diberikan valid.
5. Menguji Aplikasi
Jalankan aplikasi menggunakan perintah berikut:
1go run main.goCobalah akses endpoint berikut menggunakan alat seperti curl atau Postman.
5.1. Endpoint Publik (Tanpa Autentikasi)
1curl http://localhost:8080/publicResponsnya:
1Endpoint publik tidak membutuhkan autentikasi!5.2. Endpoint Privat (Dengan Autentikasi)
Untuk mengakses endpoint privat, tambahkan header Authorization dengan nilai Basic diikuti kredensial username dan password yang dienkode dalam Base64.
Misalnya, untuk username admin dan password password123:
1curl -H "Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM=" http://localhost:8080/privateJika valid, responsnya adalah:
1Selamat datang di endpoint privat! Anda berhasil terautentikasi.Jika kredensial salah, Anda akan menerima respons error 401 Unauthorized.
Kesimpulan
Dengan langkah-langkah di atas, Anda telah berhasil membuat middleware Basic Authentication di Golang menggunakan httprouter. Middleware ini adalah solusi sederhana untuk melindungi endpoint API, meskipun kurang aman untuk penggunaan di lingkungan produksi jika tidak dilengkapi protokol HTTPS.
Untuk pengamanan lebih baik, pertimbangkan menggunakan metode autentikasi yang lebih modern, seperti OAuth atau JWT, di aplikasi produksi Anda. Selamat mencoba!