Golang Integration
Golang Integration
This guide covers how to integrate Skycloak authentication into Go applications using various middleware options and best practices for different frameworks.
Prerequisites
- Go 1.18+
- Skycloak cluster with configured realm and client
- Basic understanding of Go HTTP handlers and middleware
Quick Start
1. Install Dependencies
Using go-oidc (recommended):
go get github.com/coreos/go-oidc/v3/oidc
go get golang.org/x/oauth2Or using gocloak for Keycloak-specific features:
go get github.com/Nerzal/gocloak/v132. 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
// 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
// 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
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
// 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
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
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
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
// 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
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
// 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
// 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
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
// Enable debug logging
func init() {
if os.Getenv("AUTH_DEBUG") == "true" {
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{})
}
}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