From b5b4763b45cd7788eed41c17f30cc025b2efd891 Mon Sep 17 00:00:00 2001 From: renaldybrada Date: Fri, 13 Feb 2026 15:43:05 +0700 Subject: [PATCH] initiate keycloak token checking --- .env.example | 8 +++- go.mod | 2 + go.sum | 4 ++ internal/middleware/authKeycloak.go | 72 +++++++++++++++++++++++++++++ internal/routes/routes.go | 8 +++- 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 internal/middleware/authKeycloak.go diff --git a/.env.example b/.env.example index e87e89d..9fbb5ba 100644 --- a/.env.example +++ b/.env.example @@ -29,4 +29,10 @@ DB_ANTRIAN_PASSWORD=stim*RS54 DB_ANTRIAN_HOST=10.10.123.165 DB_ANTRIAN_DATABASE=satu_db DB_ANTRIAN_PORT=5000 -DB_ANTRIAN_SSLMODE=disable \ No newline at end of file +DB_ANTRIAN_SSLMODE=disable + +#KEYCLOAK CREDENTIAL +KEYCLOAK_BASE_URL=https://keycloak.example.com +KEYCLOAK_REALM=myrealm +KEYCLOAK_AUDIENCE=my-client-id +KEYCLOAK_ISSUER=https://keycloak.example.com/realms/myrealm \ No newline at end of file diff --git a/go.mod b/go.mod index 9d975cd..2cc3902 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.3 require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -21,6 +22,7 @@ require ( github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect diff --git a/go.sum b/go.sum index 3a17f4f..2c15f51 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= @@ -45,6 +47,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/internal/middleware/authKeycloak.go b/internal/middleware/authKeycloak.go new file mode 100644 index 0000000..91a071e --- /dev/null +++ b/internal/middleware/authKeycloak.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "fmt" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/MicahParks/keyfunc" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v4" +) + +func AuthKeycloak() (gin.HandlerFunc, error) { + baseURL := os.Getenv("KEYCLOAK_BASE_URL") + realm := os.Getenv("KEYCLOAK_REALM") + audience := os.Getenv("KEYCLOAK_AUDIENCE") + issuer := os.Getenv("KEYCLOAK_ISSUER") + + jwksURL := fmt.Sprintf( + "%s/realms/%s/protocol/openid-connect/certs", + baseURL, + realm, + ) + + jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{ + RefreshInterval: time.Hour, + RefreshErrorHandler: func(err error) { + fmt.Println("JWKS refresh error:", err) + }, + }) + if err != nil { + return nil, err + } + + return func(c *gin.Context) { + auth := c.GetHeader("Authorization") + if auth == "" { + c.AbortWithStatusJSON(401, gin.H{"message": "missing token"}) + return + } + + tokenStr := strings.TrimPrefix(auth, "Bearer ") + + token, err := jwt.Parse(tokenStr, jwks.Keyfunc) + if err != nil || !token.Valid { + c.AbortWithStatusJSON(401, gin.H{"message": err.Error()}) + return + } + + claims := token.Claims.(jwt.MapClaims) + log.Println(claims) + + // validate issuer + if claims["iss"] != issuer { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid issuer"}) + return + } + + // validate audience + if !claims.VerifyAudience(audience, true) { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid audience"}) + return + } + + c.Set("claims", claims) + + c.Next() + }, nil +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index a9d7440..481c220 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -12,6 +12,7 @@ import ( "antrian-operasi/internal/domain/reference/spesialis" "antrian-operasi/internal/domain/reference/tindakan" "antrian-operasi/internal/middleware" + "log" "net/http" "github.com/gin-gonic/gin" @@ -34,7 +35,12 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine // init middleware router.Use(middleware.SecureCORSConfig(cfg.Security)) - api := router.Group("/api") + authKeycloak, err := middleware.AuthKeycloak() + if err != nil { + log.Fatalf("Unable to initiate keycloak auth") + } + + api := router.Group("/api", authKeycloak) antrian := api.Group("/antrian-operasi") {