Golang Integration
Ce contenu n’est pas encore disponible dans votre langue.
This guide covers how to integrate Skycloak authentication into Go applications using various middleware options and best practices for different frameworks.
Prerequisites
Section titled “Prerequisites”- Go 1.18+
- Skycloak cluster with configured realm and client
- Basic understanding of Go HTTP handlers and middleware
Quick Start
Section titled “Quick Start”1. Install Dependencies
Section titled “1. Install Dependencies”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
2. Basic OAuth2 Configuration
Section titled “2. Basic OAuth2 Configuration”// 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
}
3. HTTP Handlers
Section titled “3. HTTP Handlers”// 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
}
4. Authentication Middleware
Section titled “4. Authentication Middleware”// 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
}
Framework Integration
Section titled “Framework Integration”Gin Framework
Section titled “Gin Framework”// 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"}})
})
}
}
}
Echo Framework
Section titled “Echo Framework”// 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)
})
}
Service-to-Service Authentication
Section titled “Service-to-Service Authentication”Client Credentials Flow
Section titled “Client Credentials Flow”// 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)
}
JWT Validation
Section titled “JWT Validation”Custom JWT Validator
Section titled “Custom JWT Validator”// 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
Section titled “Testing”Mock Auth Server
Section titled “Mock Auth Server”// 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()
}
Integration Tests
Section titled “Integration Tests”// 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)
}
}
Production Considerations
Section titled “Production Considerations”Circuit Breaker
Section titled “Circuit Breaker”// 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 and Monitoring
Section titled “Metrics and Monitoring”// 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 Checks
Section titled “Health Checks”// 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
}
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”-
Token Verification Failures
- Verify JWKS URL is accessible
- Check clock synchronization
- Ensure correct signing algorithm
-
Session Management
- Use secure session stores (Redis, etc.)
- Implement proper session timeout
- Handle concurrent session access
-
Performance Issues
- Cache JWKS keys
- Implement token caching
- Use connection pooling
Debug Logging
Section titled “Debug Logging”// Enable debug logging
func init() {
if os.Getenv("AUTH_DEBUG") == "true" {
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{})
}
}
Next Steps
Section titled “Next Steps”- Configure Security Settings - Add extra security layers to your application
- Set Up Applications - Configure your Go service client settings
- User Management - Manage users and their access
- Explore Other Integrations - See integration guides for other platforms