Features

Authentication

Work with AuthContext, generate and parse JWTs, hash passwords with PBKDF2, and plug in custom auth middleware.

Ginboot provides robust tools for handling authentication, including JWT management, password encoding, and a custom AuthContext for easy access to authenticated user information.

API Request Context and Authentication

The ginboot.Context extends Gin's context with utilities to simplify authentication-related tasks. The GetAuthContext() method allows you to retrieve details about the authenticated user.

AuthContext Structure

type AuthContext struct {
	UserID    string
	UserEmail string
	Roles     []string
	Claims    map[string]interface{}
}

Retrieving AuthContext

To use GetAuthContext(), an authentication middleware must first populate the underlying gin.Context with user_id and role values. If these are not found, GetAuthContext() will return an error and the request will be aborted with a 401 Unauthorized status.

func (c *Controller) GetProtectedData(ctx *ginboot.Context) (interface{}, error) {
    authContext, err := ctx.GetAuthContext()
    if err != nil {
        // Error already handled by SendError in wrapHandler
        return nil, err 
    }

    fmt.Printf("Authenticated User ID: %s, Role: %v\n", authContext.UserID, authContext.Roles)
    // ... use authContext.UserID or authContext.Roles ...
    return gin.H{"message": "Protected data for " + authContext.UserID}, nil
}

JWT (JSON Web Token) Management

Ginboot includes utilities in the jwt.go package for generating, parsing, and validating JWTs. These functions rely on environment variables for secret keys.

Environment Variables

  • JWT_SECRET: Secret key for signing and verifying access tokens.
  • JWT_REFRESH_SECRET: Secret key for signing and verifying refresh tokens.

Generating Tokens

Use GenerateTokens to create a pair of access and refresh tokens for a given user ID and role.

import (
	"fmt"
	"github.com/klass-lk/ginboot"
	os
)

func init() {
	// Set environment variables for demonstration
	os.Setenv("JWT_SECRET", "supersecretaccesskey")
	os.Setenv("JWT_REFRESH_SECRET", "supersecretrefreshkey")
}

func main() {
	accessToken, refreshToken, err := ginboot.GenerateTokens("user123", "admin")
	if err != nil {
		fmt.Println("Error generating tokens:", err)
		return
	}
	fmt.Println("Access Token:", accessToken)
	fmt.Println("Refresh Token:", refreshToken)
}

Parsing and Extracting Claims

You can parse tokens and extract their claims to retrieve user information.

import (
	"fmt"
	"github.com/klass-lk/ginboot"
	os
)

func init() {
	// Set environment variables for demonstration
	os.Setenv("JWT_SECRET", "supersecretaccesskey")
	os.Setenv("JWT_REFRESH_SECRET", "supersecretrefreshkey")
}

func main() {
	accessToken, _, _ := ginboot.GenerateTokens("user123", "admin")

	parsedToken, err := ginboot.ParseAccessToken(accessToken)
	if err != nil {
		fmt.Println("Error parsing token:", err)
		return
	}

	claims, err := ginboot.ExtractClaims(parsedToken)
	if err != nil {
		fmt.Println("Error extracting claims:", err)
		return
	}

	userID := ginboot.ExtractUserId(claims)
	role := ginboot.ExtractRole(claims)
	fmt.Printf("Extracted User ID: %s, Role: %s\n", userID, role)

	if ginboot.IsExpired(claims) {
		fmt.Println("Token is expired")
	} else {
		fmt.Println("Token is valid")
	}
}

Password Encoding

Ginboot provides a PasswordEncoder interface and a PBKDF2Encoder implementation for secure password hashing and verification.

PasswordEncoder Interface

type PasswordEncoder interface {
    GetPasswordHash(password string) (string, error)
    IsMatching(hash, password string) bool
}

PBKDF2Encoder

This implementation uses PBKDF2 with SHA512 for strong password hashing. It requires specific environment variables for configuration.

Environment Variables

  • PBKDF2_ENCODER_SECRET: A secret string used as a salt for hashing.
  • PBKDF2_ENCODER_ITERATION: The number of iterations for the PBKDF2 algorithm (e.g., 10000).
  • PBKDF2_ENCODER_KEY_LENGTH: The desired length of the derived key (e.g., 32).

Usage Example

import (
	"fmt"
	"github.com/klass-lk/ginboot"
	os
)

func init() {
	// Set environment variables for demonstration
	os.Setenv("PBKDF2_ENCODER_SECRET", "randomsaltstring")
	os.Setenv("PBKDF2_ENCODER_ITERATION", "10000")
	os.Setenv("PBKDF2_ENCODER_KEY_LENGTH", "32")
}

func main() {
	encoder := ginboot.NewPBKDF2Encoder()

	password := "mySecurePassword123"
	hashedPassword, err := encoder.GetPasswordHash(password)
	if err != nil {
		fmt.Println("Error hashing password:", err)
		return
	}
	fmt.Println("Hashed Password:", hashedPassword)

	// Verify a matching password
	if encoder.IsMatching(hashedPassword, password) {
		fmt.Println("Password matches!")
	} else {
		fmt.Println("Password does NOT match.")
	}

	// Verify a non-matching password
	if encoder.IsMatching(hashedPassword, "wrongpassword") {
		fmt.Println("Wrong password matches (ERROR)!")
	} else {
		fmt.Println("Wrong password does not match (CORRECT).")
	}
}

Integrating Custom Authentication Middleware

To integrate authentication into your Ginboot application, you typically create a Gin middleware that processes authentication credentials (e.g., JWTs from headers) and populates the gin.Context with user information. This information can then be accessed via ginboot.Context.GetAuthContext().

Here's an example of a simple JWT authentication middleware:

package middleware

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/klass-lk/ginboot"
)

func JWTAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
			return
		}

		tokenString := strings.TrimPrefix(authHeader, "Bearer ")
		if tokenString == authHeader {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Bearer token not found"})
			return
		}

		token, err := ginboot.ParseAccessToken(tokenString)
		if err != nil || !token.Valid {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
			return
		}

		claims, err := ginboot.ExtractClaims(token)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
			return
		}

		// Set user information in Gin context for ginboot.Context.GetAuthContext()
		c.Set("user_id", ginboot.ExtractUserId(claims))
		c.Set("role", ginboot.ExtractRole(claims))
		// Optionally set other claims or user details
		// c.Set("user_email", claims["email"])
		// c.Set("claims", claims)

		c.Next()
	}
}

This middleware can then be applied globally, to a group, or to specific routes as described in the Routing Documentation.

On this page