Skip to content

Instantly share code, notes, and snippets.

@Luigi-Pizzolito
Created November 10, 2025 22:38
Show Gist options
  • Select an option

  • Save Luigi-Pizzolito/e54aa9a383ef4f13047320f587457481 to your computer and use it in GitHub Desktop.

Select an option

Save Luigi-Pizzolito/e54aa9a383ef4f13047320f587457481 to your computer and use it in GitHub Desktop.
Hank Auth Go Middleware
package auth
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// ** Sample Usage **
// // Initialize Hanko validator from env
// hankoURL := os.Getenv("HANKO_API_URL")
// var validator auth.SessionValidator
// if hankoURL != "" {
// validator = auth.NewHankoSessionValidator(hankoURL)
// } else {
// log.Fatalf("HANKO_API_URL not set; cannot start")
// }
// // Protected routes require valid hanko cookie
// protected := api.Group("")
// protected.Use(auth.Middleware(validator))
// SessionValidator defines the interface for session validation
// and optional user extraction.
// We keep it minimal per Hanko quickstart.
type SessionValidator interface {
ValidateSession(token string) (bool, *Claims, error)
}
type HankoSessionValidator struct {
APIURL string
// Optional: HTTP client injection for testing
Client *http.Client
}
type ValidationResponse struct {
IsValid bool `json:"is_valid"`
ExpirationTime *string `json:"expiration_time,omitempty"`
UserID *string `json:"user_id,omitempty"`
Claims *Claims `json:"claims,omitempty"`
}
// Claims is a subset of Hanko claims we care about.
type Claims struct {
Subject string `json:"subject"`
IssuedAt string `json:"issued_at"`
Expiration string `json:"expiration"`
Issuer string `json:"issuer"`
// Username fields (presence depends on Hanko config/claims mapping)
Username string `json:"username,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
Name string `json:"name,omitempty"`
Email *struct {
Address string `json:"address"`
IsPrimary bool `json:"is_primary"`
IsVerified bool `json:"is_verified"`
} `json:"email,omitempty"`
SessionID string `json:"session_id"`
}
func NewHankoSessionValidator(apiURL string) *HankoSessionValidator {
return &HankoSessionValidator{APIURL: strings.TrimRight(apiURL, "/"), Client: http.DefaultClient}
}
func (v *HankoSessionValidator) ValidateSession(token string) (bool, *Claims, error) {
payload := strings.NewReader(fmt.Sprintf(`{"session_token":"%s"}`, token))
req, err := http.NewRequest(http.MethodPost, v.APIURL+"/sessions/validate", payload)
if err != nil {
return false, nil, fmt.Errorf("failed to create request: %w", err)
}
req = req.WithContext(context.Background())
req.Header.Add("Content-Type", "application/json")
resp, err := v.Client.Do(req)
if err != nil {
return false, nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return false, nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return false, nil, fmt.Errorf("hanko validate status %d: %s", resp.StatusCode, string(b))
}
var vr ValidationResponse
if err := json.Unmarshal(b, &vr); err != nil {
return false, nil, fmt.Errorf("failed to parse response: %w", err)
}
return vr.IsValid, vr.Claims, nil
}
// Gin middleware that enforces valid hanko session cookie.
// On success, sets c.Set("auth", true). You may extend to set user info.
func Middleware(validator SessionValidator) gin.HandlerFunc {
return func(c *gin.Context) {
cookie, err := c.Request.Cookie("hanko")
if err != nil || cookie == nil || cookie.Value == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized", "message": "missing hanko cookie"}})
return
}
ok, claims, err := validator.ValidateSession(cookie.Value)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "auth_error", "message": err.Error()}})
return
}
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized", "message": "invalid session"}})
return
}
c.Set("auth", true)
if claims != nil {
c.Set("user.subject", claims.Subject)
if claims.Email != nil {
c.Set("user.email", claims.Email.Address)
c.Set("user.email_verified", claims.Email.IsVerified)
}
// Populate a display name if available from claims
displayName := ""
if claims.Username != "" {
displayName = claims.Username
} else if claims.PreferredUsername != "" {
displayName = claims.PreferredUsername
} else if claims.Name != "" {
displayName = claims.Name
}
if displayName != "" {
c.Set("user.name", displayName)
}
c.Set("user.session_id", claims.SessionID)
}
c.Next()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment