Authentication
Authentication
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.