go-guardian

authentication-libraryGomulti-factor-authenticationsecurityJWTbasic-authAPI-authentication

Authentication Library

go-guardian

Overview

go-guardian is a comprehensive authentication and authorization library for Go. It provides multiple authentication strategies (JWT, Basic Auth, API Key, OTP, etc.) through a unified interface and supports multi-factor authentication and custom authentication strategies. While offering a simple API, it enables implementation of advanced security features with the flexibility and scalability suitable for enterprise-level applications. Its framework-agnostic design allows use with various Go web frameworks including net/http, Gin, Echo, and others. As of 2025, it continues to be actively developed and is a reliable library trusted by the community.

Details

go-guardian is a Go library for unified handling of authentication and authorization. It offers the following key features:

  • Diverse Authentication Strategies: Support for JWT, Basic Auth, Bearer Token, API Key, OTP, and X.509 certificate authentication
  • Multi-Factor Authentication: 2FA/MFA implementation with TOTP, HOTP, SMS authentication, etc.
  • Custom Strategy Support: Easy implementation and integration of proprietary authentication mechanisms
  • Framework Agnostic: Wide compatibility from standard net/http to various frameworks
  • Caching Features: High-speed authentication results through Redis and memory caching
  • User Information Management: Flexible user data structure and permission management

Pros and Cons

Pros

  • Multiple authentication methods can be managed through a unified API, making implementation concise
  • Can handle enterprise-level security requirements
  • High portability due to flexible design that doesn't depend on frameworks
  • Easy implementation of custom authentication strategies with excellent extensibility
  • Enhanced security through standard support for multi-factor authentication
  • High performance through caching functionality

Cons

  • High initial learning cost due to extensive functionality
  • Many configuration options require time for proper setup
  • Cannot be used with languages other than Go
  • Excessive feature set for simple authentication requirements

Reference Pages

Code Examples

Basic Setup and Basic Authentication

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/shaj13/go-guardian/v2/auth"
	"github.com/shaj13/go-guardian/v2/auth/strategies/basic"
	"github.com/shaj13/go-guardian/v2/auth/strategies/bearer"
)

// User information definition
type User struct {
	ID       string
	UserName string
	Email    string
	Roles    []string
}

// Implementation of auth.Info interface
func (u User) GetID() string {
	return u.ID
}

func (u User) GetUserName() string {
	return u.UserName
}

func main() {
	keeper := auth.New()
	cache := auth.NewFIFO(context.Background(), time.Minute*5)

	// Basic authentication strategy setup
	basicStrategy := basic.New(validateUser, cache)
	keeper.EnableStrategy(basic.StrategyKey, basicStrategy)

	// Bearer Token authentication strategy setup
	bearerStrategy := bearer.New(validateToken, cache)
	keeper.EnableStrategy(bearer.CachedStrategyKey, bearerStrategy)

	// HTTP handler setup
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		user, err := keeper.Authenticate(r)
		if err != nil {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		fmt.Fprintf(w, "Hello %s, ID: %s", user.GetUserName(), user.GetID())
	})

	http.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
		user, err := keeper.Authenticate(r)
		if err != nil {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		// Role-based access control
		if u, ok := user.(User); ok {
			if !hasRole(u.Roles, "admin") {
				http.Error(w, "Forbidden", http.StatusForbidden)
				return
			}
		}

		fmt.Fprintf(w, "Admin area for %s", user.GetUserName())
	})

	http.ListenAndServe(":8080", nil)
}

// Basic authentication user validation
func validateUser(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
	// In actual implementation, retrieve authentication info from database
	users := map[string]string{
		"admin": "admin123",
		"user":  "user123",
	}

	if validPassword, exists := users[userName]; exists && validPassword == password {
		return User{
			ID:       userName,
			UserName: userName,
			Email:    userName + "@example.com",
			Roles:    getRoles(userName),
		}, nil
	}

	return nil, fmt.Errorf("invalid credentials")
}

// Bearer Token validation
func validateToken(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
	// Simple token validation (implement JWT validation etc. in practice)
	validTokens := map[string]User{
		"admin-token": {
			ID:       "admin",
			UserName: "admin",
			Email:    "[email protected]",
			Roles:    []string{"admin", "user"},
		},
		"user-token": {
			ID:       "user",
			UserName: "user",
			Email:    "[email protected]",
			Roles:    []string{"user"},
		},
	}

	if user, exists := validTokens[token]; exists {
		return user, nil
	}

	return nil, fmt.Errorf("invalid token")
}

func hasRole(roles []string, targetRole string) bool {
	for _, role := range roles {
		if role == targetRole {
			return true
		}
	}
	return false
}

func getRoles(userName string) []string {
	if userName == "admin" {
		return []string{"admin", "user"}
	}
	return []string{"user"}
}

JWT Authentication Strategy Implementation

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/shaj13/go-guardian/v2/auth"
	"github.com/shaj13/go-guardian/v2/auth/strategies/jwt"
)

var jwtSecret = []byte("your-secret-key")

type CustomClaims struct {
	UserID string   `json:"user_id"`
	Roles  []string `json:"roles"`
	jwt.StandardClaims
}

func setupJWTAuth() auth.Strategy {
	// JWT key function
	keyfunc := func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return jwtSecret, nil
	}

	// Custom claims parsing
	parser := jwt.NewParser(jwt.NewWithClaims(jwt.SigningMethodHS256, &CustomClaims{}))

	// Create JWT strategy
	strategy := jwt.New(cache, keyfunc)
	strategy.SetParser(jwt.SetExpectedIssuer("your-app"), jwt.SetExpectedAudience("your-api"))

	return strategy
}

// JWT token generation
func generateJWT(userID string, roles []string) (string, error) {
	claims := CustomClaims{
		UserID: userID,
		Roles:  roles,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
			Issuer:    "your-app",
			Audience:  "your-api",
			IssuedAt:  time.Now().Unix(),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

// JWT authentication user information parsing
func parseJWTUser(ctx context.Context, r *http.Request, token *jwt.Token) (auth.Info, error) {
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return User{
			ID:       claims.UserID,
			UserName: claims.UserID,
			Email:    claims.UserID + "@example.com",
			Roles:    claims.Roles,
		}, nil
	}
	return nil, fmt.Errorf("invalid token")
}

// Login endpoint
func loginHandler(w http.ResponseWriter, r *http.Request) {
	username := r.FormValue("username")
	password := r.FormValue("password")

	// User authentication (simple example)
	if username == "admin" && password == "admin123" {
		token, err := generateJWT("admin", []string{"admin", "user"})
		if err != nil {
			http.Error(w, "Failed to generate token", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, `{"token": "%s", "expires_in": 86400}`, token)
	} else {
		http.Error(w, "Invalid credentials", http.StatusUnauthorized)
	}
}

Multi-Factor Authentication (MFA) Implementation

package main

import (
	"context"
	"crypto/rand"
	"encoding/base32"
	"fmt"
	"time"

	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	"github.com/shaj13/go-guardian/v2/auth"
	"github.com/shaj13/go-guardian/v2/auth/strategies/twofactor"
)

type MFAUser struct {
	ID        string
	UserName  string
	Email     string
	Roles     []string
	TOTPSecret string
	MFAEnabled bool
}

func (u MFAUser) GetID() string {
	return u.ID
}

func (u MFAUser) GetUserName() string {
	return u.UserName
}

// TOTP setup generation
func generateTOTP(userID string) (*otp.Key, error) {
	return totp.Generate(totp.GenerateOpts{
		Issuer:      "YourApp",
		AccountName: userID,
		Period:      30,
		Digits:      6,
		Algorithm:   otp.AlgorithmSHA1,
	})
}

// TOTP validation
func validateTOTP(secret, token string) bool {
	return totp.Validate(token, secret)
}

// Two-factor authentication strategy setup
func setup2FA() auth.Strategy {
	// Primary authentication (Basic auth)
	primary := basic.New(validateUserForMFA, cache)

	// Secondary authentication (TOTP)
	secondary := twofactor.New(validateMFAToken, cache)

	// Two-factor authentication strategy
	return twofactor.NewStrategy(primary, secondary)
}

// MFA-enabled user validation
func validateUserForMFA(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
	// Retrieve user information from database (simple example)
	users := map[string]MFAUser{
		"admin": {
			ID:         "admin",
			UserName:   "admin",
			Email:      "[email protected]",
			Roles:      []string{"admin", "user"},
			TOTPSecret: "JBSWY3DPEHPK3PXP", // In practice, store encrypted
			MFAEnabled: true,
		},
	}

	if user, exists := users[userName]; exists {
		// Password validation (in practice, compare with hashed password)
		if password == "admin123" {
			return user, nil
		}
	}

	return nil, fmt.Errorf("invalid credentials")
}

// MFA token validation
func validateMFAToken(ctx context.Context, r *http.Request, user auth.Info, token string) (auth.Info, error) {
	mfaUser, ok := user.(MFAUser)
	if !ok {
		return nil, fmt.Errorf("invalid user type")
	}

	if !mfaUser.MFAEnabled {
		return user, nil // Pass through if MFA is disabled
	}

	if validateTOTP(mfaUser.TOTPSecret, token) {
		return user, nil
	}

	return nil, fmt.Errorf("invalid MFA token")
}

// MFA setup endpoint
func setupMFAHandler(w http.ResponseWriter, r *http.Request) {
	userID := r.FormValue("user_id")
	if userID == "" {
		http.Error(w, "User ID required", http.StatusBadRequest)
		return
	}

	key, err := generateTOTP(userID)
	if err != nil {
		http.Error(w, "Failed to generate TOTP", http.StatusInternalServerError)
		return
	}

	// Generate URL for QR code
	qrURL := key.URL()

	w.Header().Set("Content-Type", "application/json")
	fmt.Fprintf(w, `{
	"secret": "%s",
	"qr_url": "%s",
	"manual_entry_key": "%s"
}`, key.Secret(), qrURL, key.Secret())
}

Custom Authentication Strategy Implementation

package main

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"strconv"
	"time"

	"github.com/shaj13/go-guardian/v2/auth"
)

// API Key authentication strategy
type APIKeyStrategy struct {
	cache auth.Cache
}

func NewAPIKeyStrategy(cache auth.Cache) *APIKeyStrategy {
	return &APIKeyStrategy{cache: cache}
}

// Implementation of auth.Strategy interface
func (a *APIKeyStrategy) Authenticate(ctx context.Context, r *http.Request) (auth.Info, error) {
	apiKey := r.Header.Get("X-API-Key")
	timestamp := r.Header.Get("X-Timestamp")
	signature := r.Header.Get("X-Signature")

	if apiKey == "" || timestamp == "" || signature == "" {
		return nil, fmt.Errorf("missing required headers")
	}

	// Check cache
	if info, ok := a.cache.Load(apiKey); ok {
		return info, nil
	}

	// Timestamp validation (within 5 minutes)
	ts, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid timestamp")
	}

	if time.Now().Unix()-ts > 300 {
		return nil, fmt.Errorf("request too old")
	}

	// Signature validation
	if !a.validateSignature(apiKey, timestamp, signature, r) {
		return nil, fmt.Errorf("invalid signature")
	}

	// Get user information from API Key
	user, err := a.getUserByAPIKey(apiKey)
	if err != nil {
		return nil, err
	}

	// Store in cache
	a.cache.Store(apiKey, user, time.Minute*5)

	return user, nil
}

func (a *APIKeyStrategy) validateSignature(apiKey, timestamp, signature string, r *http.Request) bool {
	// Get secret key (from database in practice)
	secret := a.getSecretByAPIKey(apiKey)
	if secret == "" {
		return false
	}

	// Create signature string
	method := r.Method
	path := r.URL.Path
	query := r.URL.RawQuery
	signString := fmt.Sprintf("%s\n%s\n%s\n%s\n%s", method, path, query, timestamp, apiKey)

	// Generate signature with HMAC-SHA256
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(signString))
	expectedSignature := hex.EncodeToString(h.Sum(nil))

	return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

func (a *APIKeyStrategy) getUserByAPIKey(apiKey string) (auth.Info, error) {
	// Get user corresponding to API Key from database
	apiKeys := map[string]User{
		"api-key-123": {
			ID:       "api-user-1",
			UserName: "api-user",
			Email:    "[email protected]",
			Roles:    []string{"api"},
		},
	}

	if user, exists := apiKeys[apiKey]; exists {
		return user, nil
	}

	return nil, fmt.Errorf("invalid API key")
}

func (a *APIKeyStrategy) getSecretByAPIKey(apiKey string) string {
	// Get secret key corresponding to API Key
	secrets := map[string]string{
		"api-key-123": "secret-key-456",
	}

	return secrets[apiKey]
}

// Usage example of custom strategy
func useCustomStrategy() {
	keeper := auth.New()
	cache := auth.NewFIFO(context.Background(), time.Minute*10)

	// Register custom API Key strategy
	apiKeyStrategy := NewAPIKeyStrategy(cache)
	keeper.EnableStrategy("api-key", apiKeyStrategy)

	// Combine with other strategies
	basicStrategy := basic.New(validateUser, cache)
	keeper.EnableStrategy(basic.StrategyKey, basicStrategy)

	http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
		user, err := keeper.Authenticate(r)
		if err != nil {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		fmt.Fprintf(w, "Data accessed by: %s", user.GetUserName())
	})
}

Integration with Gin Framework

package main

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

	"github.com/gin-gonic/gin"
	"github.com/shaj13/go-guardian/v2/auth"
	"github.com/shaj13/go-guardian/v2/auth/strategies/basic"
)

// Gin middleware
func authMiddleware(keeper auth.Authenticator) gin.HandlerFunc {
	return func(c *gin.Context) {
		user, err := keeper.Authenticate(c.Request)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error": "Authentication failed",
				"message": err.Error(),
			})
			c.Abort()
			return
		}

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

// Role restriction middleware
func requireRole(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 in context"})
			c.Abort()
			return
		}

		if u, ok := user.(User); ok {
			if !hasRole(u.Roles, role) {
				c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
				c.Abort()
				return
			}
		}

		c.Next()
	}
}

func setupGinApp() {
	r := gin.Default()

	// Authentication setup
	keeper := auth.New()
	cache := auth.NewFIFO(context.Background(), time.Minute*5)
	basicStrategy := basic.New(validateUser, cache)
	keeper.EnableStrategy(basic.StrategyKey, basicStrategy)

	// Public endpoints
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "Welcome to the API"})
	})

	// Endpoints requiring authentication
	auth := r.Group("/auth")
	auth.Use(authMiddleware(keeper))
	{
		auth.GET("/profile", func(c *gin.Context) {
			user, _ := c.Get("user")
			c.JSON(http.StatusOK, gin.H{"user": user})
		})

		// Admin-only access
		admin := auth.Group("/admin")
		admin.Use(requireRole("admin"))
		{
			admin.GET("/users", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"users": []string{"user1", "user2"}})
			})
		}
	}

	r.Run(":8080")
}