Zum Inhalt springen

Golang Integration

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

This guide covers how to integrate Skycloak authentication into Go applications using various middleware options and best practices for different frameworks.

  • Go 1.18+
  • Skycloak cluster with configured realm and client
  • Basic understanding of Go HTTP handlers and middleware

Using go-oidc (recommended):

go get github.com/coreos/go-oidc/v3/oidc
go get golang.org/x/oauth2

Or using gocloak for Keycloak-specific features:

go get github.com/Nerzal/gocloak/v13
// config/auth.go
package config

import (
    "context"
    "github.com/coreos/go-oidc/v3/oidc"
    "golang.org/x/oauth2"
)

type AuthConfig struct {
    Provider     *oidc.Provider
    OAuth2Config oauth2.Config
    Verifier     *oidc.IDTokenVerifier
}

func NewAuthConfig(ctx context.Context) (*AuthConfig, error) {
    // Initialize OIDC provider
    provider, err := oidc.NewProvider(ctx, "https://your-cluster-id.app.skycloak.io/realms/your-realm")
    if err != nil {
        return nil, err
    }

    // Configure OAuth2
    oauth2Config := oauth2.Config{
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
        RedirectURL:  "http://localhost:8080/callback",
        Endpoint:     provider.Endpoint(),
        Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
    }

    // Create ID token verifier
    verifier := provider.Verifier(&oidc.Config{
        ClientID: oauth2Config.ClientID,
    })

    return &AuthConfig{
        Provider:     provider,
        OAuth2Config: oauth2Config,
        Verifier:     verifier,
    }, nil
}
// handlers/auth.go
package handlers

import (
    "crypto/rand"
    "encoding/base64"
    "encoding/json"
    "net/http"
    "time"
)

type AuthHandler struct {
    config *config.AuthConfig
}

func NewAuthHandler(config *config.AuthConfig) *AuthHandler {
    return &AuthHandler{config: config}
}

// Login initiates the OAuth2 flow
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
    // Generate state for CSRF protection
    state, err := generateRandomState()
    if err != nil {
        http.Error(w, "Failed to generate state", http.StatusInternalServerError)
        return
    }

    // Store state in cookie
    http.SetCookie(w, &http.Cookie{
        Name:     "oauth_state",
        Value:    state,
        MaxAge:   300, // 5 minutes
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteLaxMode,
    })

    // Redirect to authorization endpoint
    authURL := h.config.OAuth2Config.AuthCodeURL(state)
    http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
}

// Callback handles the OAuth2 callback
func (h *AuthHandler) Callback(w http.ResponseWriter, r *http.Request) {
    // Verify state
    stateCookie, err := r.Cookie("oauth_state")
    if err != nil {
        http.Error(w, "State cookie not found", http.StatusBadRequest)
        return
    }

    if r.URL.Query().Get("state") != stateCookie.Value {
        http.Error(w, "Invalid state", http.StatusBadRequest)
        return
    }

    // Exchange code for token
    code := r.URL.Query().Get("code")
    token, err := h.config.OAuth2Config.Exchange(r.Context(), code)
    if err != nil {
        http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
        return
    }

    // Extract and verify ID token
    rawIDToken, ok := token.Extra("id_token").(string)
    if !ok {
        http.Error(w, "No ID token found", http.StatusInternalServerError)
        return
    }

    idToken, err := h.config.Verifier.Verify(r.Context(), rawIDToken)
    if err != nil {
        http.Error(w, "Failed to verify ID token", http.StatusInternalServerError)
        return
    }

    // Create session
    session := &Session{
        AccessToken:  token.AccessToken,
        RefreshToken: token.RefreshToken,
        IDToken:      rawIDToken,
        Expiry:       token.Expiry,
    }

    // Store session (implementation depends on your session store)
    sessionID, err := storeSession(session)
    if err != nil {
        http.Error(w, "Failed to create session", http.StatusInternalServerError)
        return
    }

    // Set session cookie
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    sessionID,
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteLaxMode,
        MaxAge:   86400, // 24 hours
    })

    // Redirect to dashboard
    http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
}

// Logout handles user logout
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
    // Clear session
    sessionCookie, err := r.Cookie("session_id")
    if err == nil {
        deleteSession(sessionCookie.Value)
    }

    // Clear session cookie
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    "",
        MaxAge:   -1,
        HttpOnly: true,
        Secure:   true,
    })

    // Redirect to Keycloak logout
    logoutURL := fmt.Sprintf("%s/protocol/openid-connect/logout?redirect_uri=%s",
        h.config.Provider.Endpoint().AuthURL[:strings.LastIndex(h.config.Provider.Endpoint().AuthURL, "/protocol")],
        url.QueryEscape("http://localhost:8080"),
    )
    
    http.Redirect(w, r, logoutURL, http.StatusTemporaryRedirect)
}

func generateRandomState() (string, error) {
    b := make([]byte, 32)
    _, err := rand.Read(b)
    if err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b), nil
}
// middleware/auth.go
package middleware

import (
    "context"
    "net/http"
    "strings"
)

type contextKey string

const (
    UserContextKey contextKey = "user"
)

type User struct {
    ID       string
    Username string
    Email    string
    Roles    []string
    Groups   []string
}

// RequireAuth middleware ensures user is authenticated
func RequireAuth(config *config.AuthConfig) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Check session cookie
            sessionCookie, err := r.Cookie("session_id")
            if err != nil {
                http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
                return
            }

            // Retrieve session
            session, err := getSession(sessionCookie.Value)
            if err != nil {
                http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
                return
            }

            // Check if token needs refresh
            if time.Until(session.Expiry) < 5*time.Minute {
                newToken, err := refreshToken(config, session.RefreshToken)
                if err != nil {
                    http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
                    return
                }
                session.AccessToken = newToken.AccessToken
                session.Expiry = newToken.Expiry
                updateSession(sessionCookie.Value, session)
            }

            // Parse ID token for user info
            idToken, err := config.Verifier.Verify(r.Context(), session.IDToken)
            if err != nil {
                http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
                return
            }

            // Extract user claims
            var claims struct {
                Sub               string   `json:"sub"`
                PreferredUsername string   `json:"preferred_username"`
                Email             string   `json:"email"`
                RealmAccess       struct {
                    Roles []string `json:"roles"`
                } `json:"realm_access"`
                Groups []string `json:"groups"`
            }
            
            if err := idToken.Claims(&claims); err != nil {
                http.Error(w, "Failed to parse claims", http.StatusInternalServerError)
                return
            }

            // Create user object
            user := &User{
                ID:       claims.Sub,
                Username: claims.PreferredUsername,
                Email:    claims.Email,
                Roles:    claims.RealmAccess.Roles,
                Groups:   claims.Groups,
            }

            // Add user to context
            ctx := context.WithValue(r.Context(), UserContextKey, user)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// RequireRole middleware ensures user has specific role
func RequireRole(role string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user, ok := r.Context().Value(UserContextKey).(*User)
            if !ok {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            hasRole := false
            for _, userRole := range user.Roles {
                if userRole == role {
                    hasRole = true
                    break
                }
            }

            if !hasRole {
                http.Error(w, "Forbidden", http.StatusForbidden)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// GetUser retrieves user from context
func GetUser(ctx context.Context) (*User, bool) {
    user, ok := ctx.Value(UserContextKey).(*User)
    return user, ok
}
// middleware/gin_auth.go
package middleware

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

// GinAuthMiddleware for Gin framework
func GinAuthMiddleware(config *config.AuthConfig) gin.HandlerFunc {
    return func(c *gin.Context) {
        // Get token from Authorization header
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authorization header"})
            c.Abort()
            return
        }

        // Extract bearer token
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header"})
            c.Abort()
            return
        }

        // Verify token
        token, err := verifyAccessToken(config, parts[1])
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        // Extract claims
        var claims map[string]interface{}
        if err := token.Claims(&claims); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse claims"})
            c.Abort()
            return
        }

        // Set user in context
        c.Set("user", claims)
        c.Next()
    }
}

// GinRequireRole middleware
func GinRequireRole(role string) gin.HandlerFunc {
    return func(c *gin.Context) {
        user, exists := c.Get("user")
        if !exists {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
            c.Abort()
            return
        }

        claims := user.(map[string]interface{})
        realmAccess, ok := claims["realm_access"].(map[string]interface{})
        if !ok {
            c.JSON(http.StatusForbidden, gin.H{"error": "No realm access"})
            c.Abort()
            return
        }

        roles, ok := realmAccess["roles"].([]interface{})
        if !ok {
            c.JSON(http.StatusForbidden, gin.H{"error": "No roles found"})
            c.Abort()
            return
        }

        hasRole := false
        for _, r := range roles {
            if r.(string) == role {
                hasRole = true
                break
            }
        }

        if !hasRole {
            c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
            c.Abort()
            return
        }

        c.Next()
    }
}

// Example Gin routes
func SetupGinRoutes(router *gin.Engine, config *config.AuthConfig) {
    // Public routes
    router.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "healthy"})
    })

    // Protected routes
    protected := router.Group("/api")
    protected.Use(GinAuthMiddleware(config))
    {
        protected.GET("/profile", func(c *gin.Context) {
            user, _ := c.Get("user")
            c.JSON(http.StatusOK, user)
        })

        // Admin only routes
        admin := protected.Group("/admin")
        admin.Use(GinRequireRole("admin"))
        {
            admin.GET("/users", func(c *gin.Context) {
                c.JSON(http.StatusOK, gin.H{"users": []string{"user1", "user2"}})
            })
        }
    }
}
// middleware/echo_auth.go
package middleware

import (
    "github.com/labstack/echo/v4"
    "net/http"
)

// EchoAuthMiddleware for Echo framework
func EchoAuthMiddleware(config *config.AuthConfig) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            // Get token from Authorization header
            authHeader := c.Request().Header.Get("Authorization")
            if authHeader == "" {
                return echo.NewHTTPError(http.StatusUnauthorized, "Missing authorization header")
            }

            // Extract bearer token
            parts := strings.Split(authHeader, " ")
            if len(parts) != 2 || parts[0] != "Bearer" {
                return echo.NewHTTPError(http.StatusUnauthorized, "Invalid authorization header")
            }

            // Verify token
            token, err := verifyAccessToken(config, parts[1])
            if err != nil {
                return echo.NewHTTPError(http.StatusUnauthorized, "Invalid token")
            }

            // Extract claims
            var claims map[string]interface{}
            if err := token.Claims(&claims); err != nil {
                return echo.NewHTTPError(http.StatusInternalServerError, "Failed to parse claims")
            }

            // Set user in context
            c.Set("user", claims)
            return next(c)
        }
    }
}

// Example Echo routes
func SetupEchoRoutes(e *echo.Echo, config *config.AuthConfig) {
    // Public routes
    e.GET("/health", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{"status": "healthy"})
    })

    // Protected routes
    api := e.Group("/api")
    api.Use(EchoAuthMiddleware(config))
    
    api.GET("/profile", func(c echo.Context) error {
        user := c.Get("user")
        return c.JSON(http.StatusOK, user)
    })
}
// services/auth_client.go
package services

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "strings"
    "sync"
    "time"
)

type ServiceAuthClient struct {
    tokenURL     string
    clientID     string
    clientSecret string
    httpClient   *http.Client
    
    mu          sync.RWMutex
    accessToken string
    expiry      time.Time
}

func NewServiceAuthClient(tokenURL, clientID, clientSecret string) *ServiceAuthClient {
    return &ServiceAuthClient{
        tokenURL:     tokenURL,
        clientID:     clientID,
        clientSecret: clientSecret,
        httpClient:   &http.Client{Timeout: 10 * time.Second},
    }
}

// GetToken retrieves a valid access token, refreshing if necessary
func (c *ServiceAuthClient) GetToken(ctx context.Context) (string, error) {
    c.mu.RLock()
    if c.accessToken != "" && time.Now().Before(c.expiry.Add(-30*time.Second)) {
        token := c.accessToken
        c.mu.RUnlock()
        return token, nil
    }
    c.mu.RUnlock()

    // Need to refresh token
    c.mu.Lock()
    defer c.mu.Unlock()

    // Double-check after acquiring write lock
    if c.accessToken != "" && time.Now().Before(c.expiry.Add(-30*time.Second)) {
        return c.accessToken, nil
    }

    // Request new token
    data := url.Values{}
    data.Set("grant_type", "client_credentials")
    data.Set("client_id", c.clientID)
    data.Set("client_secret", c.clientSecret)

    req, err := http.NewRequestWithContext(ctx, "POST", c.tokenURL, strings.NewReader(data.Encode()))
    if err != nil {
        return "", err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return "", fmt.Errorf("token request failed with status: %d", resp.StatusCode)
    }

    var tokenResp struct {
        AccessToken string `json:"access_token"`
        ExpiresIn   int    `json:"expires_in"`
    }

    if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
        return "", err
    }

    c.accessToken = tokenResp.AccessToken
    c.expiry = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)

    return c.accessToken, nil
}

// AuthenticatedClient returns an HTTP client with automatic token injection
func (c *ServiceAuthClient) AuthenticatedClient() *http.Client {
    return &http.Client{
        Transport: &AuthTransport{
            Base:       http.DefaultTransport,
            AuthClient: c,
        },
        Timeout: 30 * time.Second,
    }
}

type AuthTransport struct {
    Base       http.RoundTripper
    AuthClient *ServiceAuthClient
}

func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    token, err := t.AuthClient.GetToken(req.Context())
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+token)
    return t.Base.RoundTrip(req)
}
// auth/jwt_validator.go
package auth

import (
    "crypto/rsa"
    "encoding/json"
    "errors"
    "fmt"
    "github.com/golang-jwt/jwt/v4"
    "net/http"
    "sync"
    "time"
)

type JWTValidator struct {
    jwksURL    string
    clientID   string
    httpClient *http.Client
    
    mu       sync.RWMutex
    keyCache map[string]*rsa.PublicKey
}

func NewJWTValidator(jwksURL, clientID string) *JWTValidator {
    return &JWTValidator{
        jwksURL:    jwksURL,
        clientID:   clientID,
        httpClient: &http.Client{Timeout: 10 * time.Second},
        keyCache:   make(map[string]*rsa.PublicKey),
    }
}

// ValidateToken validates a JWT token
func (v *JWTValidator) ValidateToken(tokenString string) (*jwt.Token, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Verify signing method
        if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }

        // Get key ID
        kid, ok := token.Header["kid"].(string)
        if !ok {
            return nil, errors.New("kid not found in token header")
        }

        // Get public key
        return v.getPublicKey(kid)
    })

    if err != nil {
        return nil, err
    }

    // Validate claims
    claims, ok := token.Claims.(jwt.MapClaims)
    if !ok {
        return nil, errors.New("invalid claims format")
    }

    // Verify audience
    if !v.verifyAudience(claims) {
        return nil, errors.New("invalid audience")
    }

    // Verify expiration
    if !v.verifyExpiration(claims) {
        return nil, errors.New("token expired")
    }

    return token, nil
}

func (v *JWTValidator) getPublicKey(kid string) (*rsa.PublicKey, error) {
    // Check cache
    v.mu.RLock()
    if key, ok := v.keyCache[kid]; ok {
        v.mu.RUnlock()
        return key, nil
    }
    v.mu.RUnlock()

    // Fetch JWKS
    resp, err := v.httpClient.Get(v.jwksURL)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var jwks struct {
        Keys []json.RawMessage `json:"keys"`
    }
    
    if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
        return nil, err
    }

    // Parse keys and update cache
    v.mu.Lock()
    defer v.mu.Unlock()

    for _, keyData := range jwks.Keys {
        key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
        if err != nil {
            continue
        }
        
        var keyInfo struct {
            Kid string `json:"kid"`
        }
        if err := json.Unmarshal(keyData, &keyInfo); err != nil {
            continue
        }
        
        v.keyCache[keyInfo.Kid] = key
    }

    if key, ok := v.keyCache[kid]; ok {
        return key, nil
    }

    return nil, errors.New("key not found")
}

func (v *JWTValidator) verifyAudience(claims jwt.MapClaims) bool {
    aud, ok := claims["aud"]
    if !ok {
        return false
    }

    switch aud := aud.(type) {
    case string:
        return aud == v.clientID
    case []interface{}:
        for _, a := range aud {
            if a == v.clientID {
                return true
            }
        }
    }
    
    return false
}

func (v *JWTValidator) verifyExpiration(claims jwt.MapClaims) bool {
    exp, ok := claims["exp"].(float64)
    if !ok {
        return false
    }
    
    return time.Now().Unix() < int64(exp)
}
// testing/mock_auth.go
package testing

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "time"
    "github.com/golang-jwt/jwt/v4"
)

type MockAuthServer struct {
    server *httptest.Server
}

func NewMockAuthServer() *MockAuthServer {
    mux := http.NewServeMux()
    server := httptest.NewServer(mux)
    
    m := &MockAuthServer{server: server}
    
    // Setup routes
    mux.HandleFunc("/realms/test/protocol/openid-connect/token", m.handleToken)
    mux.HandleFunc("/realms/test/protocol/openid-connect/certs", m.handleCerts)
    mux.HandleFunc("/realms/test/.well-known/openid-configuration", m.handleDiscovery)
    
    return m
}

func (m *MockAuthServer) handleToken(w http.ResponseWriter, r *http.Request) {
    // Generate mock token
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "sub": "test-user",
        "exp": time.Now().Add(time.Hour).Unix(),
        "realm_access": map[string]interface{}{
            "roles": []string{"user", "admin"},
        },
    })
    
    tokenString, _ := token.SignedString([]byte("test-secret"))
    
    response := map[string]interface{}{
        "access_token": tokenString,
        "token_type":   "Bearer",
        "expires_in":   3600,
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func (m *MockAuthServer) URL() string {
    return m.server.URL
}

func (m *MockAuthServer) Close() {
    m.server.Close()
}
// handlers/auth_test.go
package handlers

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestAuthMiddleware(t *testing.T) {
    // Setup mock auth server
    mockAuth := testing.NewMockAuthServer()
    defer mockAuth.Close()
    
    // Create test handler
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, ok := GetUser(r.Context())
        if !ok {
            t.Error("User not found in context")
            return
        }
        
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(map[string]string{
            "message": "Hello " + user.Username,
        })
    })
    
    // Wrap with auth middleware
    authHandler := RequireAuth(testConfig)(handler)
    
    // Test without token
    req := httptest.NewRequest("GET", "/protected", nil)
    rec := httptest.NewRecorder()
    authHandler.ServeHTTP(rec, req)
    
    if rec.Code != http.StatusTemporaryRedirect {
        t.Errorf("Expected redirect, got %d", rec.Code)
    }
    
    // Test with valid token
    req = httptest.NewRequest("GET", "/protected", nil)
    req.AddCookie(&http.Cookie{
        Name:  "session_id",
        Value: "valid-session",
    })
    rec = httptest.NewRecorder()
    authHandler.ServeHTTP(rec, req)
    
    if rec.Code != http.StatusOK {
        t.Errorf("Expected 200, got %d", rec.Code)
    }
}
// resilience/circuit_breaker.go
package resilience

import (
    "github.com/sony/gobreaker"
    "net/http"
    "time"
)

func NewAuthCircuitBreaker() *gobreaker.CircuitBreaker {
    settings := gobreaker.Settings{
        Name:        "auth-service",
        MaxRequests: 10,
        Interval:    60 * time.Second,
        Timeout:     30 * time.Second,
        ReadyToTrip: func(counts gobreaker.Counts) bool {
            failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
            return counts.Requests >= 3 && failureRatio >= 0.6
        },
        OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
            log.Printf("Circuit breaker %s: %s -> %s", name, from, to)
        },
    }
    
    return gobreaker.NewCircuitBreaker(settings)
}

// Usage with HTTP client
type ResilientAuthClient struct {
    client  *http.Client
    breaker *gobreaker.CircuitBreaker
}

func (c *ResilientAuthClient) Do(req *http.Request) (*http.Response, error) {
    resp, err := c.breaker.Execute(func() (interface{}, error) {
        return c.client.Do(req)
    })
    
    if err != nil {
        return nil, err
    }
    
    return resp.(*http.Response), nil
}
// metrics/auth_metrics.go
package metrics

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    authRequests = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "auth_requests_total",
        Help: "Total number of authentication requests",
    }, []string{"status"})
    
    authDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
        Name: "auth_request_duration_seconds",
        Help: "Duration of authentication requests",
    }, []string{"operation"})
    
    tokenRefreshes = promauto.NewCounter(prometheus.CounterOpts{
        Name: "token_refreshes_total",
        Help: "Total number of token refreshes",
    })
)

// Middleware to track auth metrics
func MetricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        timer := prometheus.NewTimer(authDuration.WithLabelValues("auth_check"))
        defer timer.ObserveDuration()
        
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(wrapped, r)
        
        authRequests.WithLabelValues(http.StatusText(wrapped.statusCode)).Inc()
    })
}
// health/auth_health.go
package health

import (
    "context"
    "net/http"
    "time"
)

type AuthHealthChecker struct {
    authURL    string
    httpClient *http.Client
}

func (h *AuthHealthChecker) Check(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", h.authURL+"/.well-known/openid-configuration", nil)
    if err != nil {
        return err
    }
    
    resp, err := h.httpClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("auth service unhealthy: status %d", resp.StatusCode)
    }
    
    return nil
}
  1. Token Verification Failures

    • Verify JWKS URL is accessible
    • Check clock synchronization
    • Ensure correct signing algorithm
  2. Session Management

    • Use secure session stores (Redis, etc.)
    • Implement proper session timeout
    • Handle concurrent session access
  3. Performance Issues

    • Cache JWKS keys
    • Implement token caching
    • Use connection pooling
// Enable debug logging
func init() {
    if os.Getenv("AUTH_DEBUG") == "true" {
        log.SetLevel(log.DebugLevel)
        log.SetFormatter(&log.JSONFormatter{})
    }
}