go-guardian
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")
}