Di dunia pemrograman modern, keamanan aplikasi menjadi hal yang sangat penting, terutama ketika melibatkan otentikasi pengguna. Salah satu metode otentikasi yang banyak digunakan saat ini adalah OAuth 2.0, yang memungkinkan aplikasi untuk mengakses sumber daya pengguna tanpa membagikan kredensial mereka. Dalam artikel ini, kita akan membahas bagaimana cara membuat middleware otentikasi menggunakan OAuth 2.0 dengan menggunakan Go dan library httprouter
.
Apa itu OAuth 2.0?
OAuth 2.0 adalah protokol otentikasi dan otorisasi yang memungkinkan aplikasi pihak ketiga untuk mendapatkan akses terbatas ke sumber daya pengguna tanpa membagikan informasi kredensial pengguna. Protokol ini banyak digunakan di layanan besar seperti Google, Facebook, dan GitHub untuk memungkinkan login pengguna menggunakan akun yang sudah ada.
Dalam implementasi ini, pengguna akan diajak untuk login melalui penyedia otentikasi (seperti Google atau GitHub), dan aplikasi kita akan mendapatkan token akses yang memungkinkan akses ke data pengguna.
Mengapa Menggunakan httprouter
?
Library httprouter
adalah router HTTP yang cepat dan ringan untuk aplikasi Go. Router ini sangat cocok digunakan dalam aplikasi Go karena performanya yang tinggi dan cara penggunaannya yang sederhana. Dengan httprouter
, kita dapat menangani permintaan HTTP dengan sangat efisien.
Pada artikel ini, kita akan memanfaatkan httprouter
untuk membuat middleware otentikasi OAuth 2.0.
Persyaratan
Sebelum memulai, pastikan Anda memiliki beberapa hal berikut:
- Go: Pastikan Anda telah menginstal Go pada sistem Anda.
- Library
httprouter
: Kita akan menggunakan library ini untuk menangani rute HTTP. - OAuth 2.0: Anda perlu mendaftar aplikasi pada penyedia OAuth (seperti Google atau GitHub) untuk mendapatkan kredensial klien (client ID dan client secret).
Instalasi httprouter
:
go get github.com/julienschmidt/httprouter
1. Menyiapkan Environment OAuth 2.0
Langkah pertama adalah membuat aplikasi di penyedia OAuth 2.0 yang Anda pilih (misalnya, Google). Anda perlu mendapatkan client ID dan client secret dari penyedia tersebut.
Jika Anda memilih Google, Anda dapat melakukannya dengan mengunjungi Google Developers Console dan membuat proyek baru. Setelah itu, buat kredensial OAuth 2.0 dan simpan client ID
dan client secret
.
2. Menggunakan Library OAuth 2.0 untuk Go
Go memiliki beberapa library untuk bekerja dengan OAuth 2.0. Salah satu yang paling populer adalah golang.org/x/oauth2
.
Untuk menginstal library ini, jalankan perintah berikut:
go get golang.org/x/oauth2
go get golang.org/x/oauth2/google
3. Membuat Middleware Authentication
Sekarang kita akan membuat middleware yang akan memeriksa token akses OAuth 2.0 dalam setiap permintaan HTTP. Jika token valid, permintaan diteruskan; jika tidak, akan mengarahkan pengguna untuk login terlebih dahulu.
Berikut adalah contoh implementasi middleware otentikasi menggunakan OAuth 2.0:
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var oauth2Config oauth2.Config
// OAuth2 middleware to check for valid token
func OAuth2(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// load token dari request header user
token, err := loadTokenFromRequest(r)
if err != nil || !token.Valid() {
http.Redirect(w, r, OauthConfig.AuthCodeURL("", oauth2.AccessTypeOffline), http.StatusNotFound)
return
}
// create token source using load token
ts := OauthConfig.TokenSource(r.Context(), token)
// optionally refresh token
token, err = ts.Token()
if err != nil || !token.Valid() {
http.Redirect(w, r, OauthConfig.AuthCodeURL("", oauth2.AccessTypeOffline), http.StatusNotFound)
}
// continue to the next handler
next(w, r, p)
}
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("error loading .env files")
}
middleware.OauthConfig = &oauth2.Config{
ClientID: os.Getenv("OAUTH_GOOGLE_CLIENT_ID"),
ClientSecret: os.Getenv("OAUTH_GOOGLE_CLIENT_SECRET"),
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"openid", "profile", "email"},
Endpoint: google.Endpoint,
}
router := httprouter.New()
// Protect /home route with OAuth2 middleware
router.GET("/home", oauth2Middleware(homeHandler))
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", router))
}
Penjelasan Kode
- oauth2.Config: Menyimpan konfigurasi OAuth 2.0, termasuk
clientID
,clientSecret
, danredirectURL
. - oauth2Middleware: Middleware ini memeriksa apakah token OAuth 2.0 yang diteruskan bersama permintaan valid. Jika tidak valid, pengguna akan diarahkan untuk login.
- homeHandler: Handler untuk route
/home
, yang hanya dapat diakses jika pengguna telah berhasil diautentikasi.
4. Menambahkan Route Callback
Setelah pengguna berhasil login, penyedia OAuth 2.0 akan mengalihkan mereka kembali ke aplikasi Anda ke URL callback yang telah Anda tentukan dalam konfigurasi (RedirectURL
). Berikut adalah handler untuk menangani callback:
func HomeOauthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "welcome, you are authenticated!\n")
}
// callback handler after success login oauth authentication
func CallbackHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
code := r.URL.Query().Get("code")
token, err := midleware.OauthConfig.Exchange(r.Context(), code)
if err != nil {
http.Error(w, "Failed to get token"+err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "You are authenticated! Token: %s", token.AccessToken)
}
Jangan lupa untuk menambahkan route untuk callback:
router.GET("/callback", callbackHandler)
5. Menambahkan Unit Test
Berikut adalah unit test untuk loadTokenFromRequest
dan OAuth2
, yang memastikan bahwa middleware berfungsi dengan benar:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_loadTokenFromRequest(t *testing.T) {
type args struct {
r *http.Request
}
// Helper to create a request with a cookie
createRequestWithToken := func(token oauth2.Token) *http.Request {
tokenBytes, _ := json.Marshal(token)
encodedToken := base64.StdEncoding.EncodeToString(tokenBytes)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_token",
Value: encodedToken,
})
return req
}
tests := []struct {
name string
args args
want *oauth2.Token
wantErr bool
}{
{
name: "successfully load token",
args: args{
r: createRequestWithToken(oauth2.Token{
AccessToken: "test_access_token",
TokenType: "Bearer",
}),
},
want: &oauth2.Token{
AccessToken: "test_access_token",
TokenType: "Bearer",
},
wantErr: false,
},
{
name: "missing cookie",
args: args{
r: httptest.NewRequest(http.MethodGet, "/", nil),
},
want: nil,
wantErr: true,
},
{
name: "invalid base64",
args: args{
r: func() *http.Request {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_token",
Value: "invalid-base64-@@@",
})
return req
}(),
},
want: nil,
wantErr: true,
},
{
name: "invalid json after base64 decoding",
args: args{
r: func() *http.Request {
req := httptest.NewRequest(http.MethodGet, "/", nil)
invalidJSON := base64.StdEncoding.EncodeToString([]byte("not a json"))
req.AddCookie(&http.Cookie{
Name: "oauth_token",
Value: invalidJSON,
})
return req
}(),
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loadTokenFromRequest(tt.args.r)
if (err != nil) != tt.wantErr {
t.Errorf("loadTokenFromRequest() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("loadTokenFromRequest() = %v, want %v", got, tt.want)
}
})
}
}
func Test_OAuth2(t *testing.T) {
type args struct {
setupRequest func() *http.Request
}
tests := []struct {
name string
args args
wantRedirect bool
wantStatusCode int
}{
{
name: "valid token proceeds to next handler",
args: args{
setupRequest: func() *http.Request {
token := oauth2.Token{
AccessToken: "valid_token",
TokenType: "Bearer",
}
tokenBytes, _ := json.Marshal(token)
encodedToken := base64.StdEncoding.EncodeToString(tokenBytes)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_token",
Value: encodedToken,
})
return req
},
},
wantRedirect: false,
wantStatusCode: http.StatusOK,
},
{
name: "missing token redirects",
args: args{
setupRequest: func() *http.Request {
return httptest.NewRequest(http.MethodGet, "/", nil)
},
},
wantRedirect: true,
wantStatusCode: http.StatusNotFound,
},
{
name: "invalid token redirects",
args: args{
setupRequest: func() *http.Request {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: "oauth_token",
Value: "invalid-base64-@@@",
})
return req
},
},
wantRedirect: true,
wantStatusCode: http.StatusNotFound,
},
}
// Mock OauthConfig
OauthConfig = &oauth2.Config{
ClientID: "client_id",
ClientSecret: "client_secret",
Endpoint: oauth2.Endpoint{},
RedirectURL: "http://localhost",
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Prepare recorder and router params
rr := httptest.NewRecorder()
req := tt.args.setupRequest()
params := httprouter.Params{}
// Define a dummy next handler
nextCalled := false
next := func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
nextCalled = true
w.WriteHeader(http.StatusOK)
}
// Wrap middleware
handler := OAuth2(next)
handler(rr, req, params)
if tt.wantRedirect {
if rr.Code != tt.wantStatusCode {
t.Errorf("expected redirect with status %d, got %d", tt.wantStatusCode, rr.Code)
}
} else {
if !nextCalled {
t.Errorf("expected next handler to be called, but it was not")
}
if rr.Code != tt.wantStatusCode {
t.Errorf("expected status %d, got %d", tt.wantStatusCode, rr.Code)
}
}
})
}
}
Untuk menjalankan unit test:
go test -v
Kesimpulan
Membuat middleware otentikasi menggunakan OAuth 2.0 dengan Go dan httprouter
adalah cara yang efektif untuk mengamankan aplikasi Anda dan memberikan akses kepada pengguna yang sudah terautentikasi. Dengan mengikuti tutorial ini, Anda telah mempelajari bagaimana cara menyiapkan OAuth 2.0, membuat middleware, serta melindungi rute di aplikasi Go Anda.
Untuk lebih banyak tutorial terkait Go, Anda dapat mengunjungi Tutorial Golang.
Juga, jika Anda tertarik untuk mempelajari lebih lanjut tentang routing HTTP di Go, silakan baca Web HTTP Router pada Golang.
12 Membuat Static File Handler
Artikel Terhangat
14 Menghapus (Delete) Data Gorm Library
03 Mar 2025
13 Advanced Update Data Gorm Library
03 Mar 2025

14 Menghapus (Delete) Data Gorm Library
