package middleware import ( "antrian-operasi/internal/config" "antrian-operasi/internal/shared" "fmt" "log" "net/http" "strings" "time" "github.com/MicahParks/keyfunc" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) func AuthKeycloak(cfg config.KeycloakConfig) (gin.HandlerFunc, error) { jwksURL := fmt.Sprintf( "%s/realms/%s/protocol/openid-connect/certs", cfg.BaseUrl, cfg.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) { // bypassing keycloak validation, if not enabled if !cfg.IsEnabled { log.Println("==== bypassing keycloak validation ====") c.Next() return } errorResponse := shared.BaseErrorResponse{ Success: false, Code: 401, } auth := c.GetHeader("Authorization") if auth == "" { errorResponse.Message = "missing token" c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse) return } tokenStr := strings.TrimPrefix(auth, "Bearer ") token, err := jwt.Parse(tokenStr, jwks.Keyfunc) if err != nil || !token.Valid { errorResponse.Message = err.Error() c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse) return } claims := token.Claims.(jwt.MapClaims) // validate issuer errorResponse.Message = "invalid keycloak configuration" if claims["iss"] != cfg.Issuer { errorResponse.Errors = []string{"invalid issuer"} c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse) return } // validate audience if !claims.VerifyAudience(cfg.Audience, true) { errorResponse.Errors = []string{"invalid audience"} c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse) return } c.Set("claims", claims) c.Next() }, nil }