Echo JWT
Authentication Library
Echo JWT
Overview
Echo JWT is a specialized JWT authentication middleware designed exclusively for Go's Echo framework. As of 2025, following security vulnerability CVE-2024-51744, it has been separated from the Echo core and is now provided as an independent package. It uses golang-jwt/jwt/v5 as its internal implementation, providing secure and efficient JWT authentication functionality for Echo applications. The library maintains compatibility with Echo v4 while addressing the latest security requirements.
Details
Echo JWT is a JWT authentication middleware that integrates seamlessly with the Echo framework. Key features include:
- Echo Native Integration: Full compatibility and optimized integration with Echo v4
- JWT v5 Support: Secure implementation using the latest golang-jwt/jwt/v5
- Automatic Validation: Automatic token extraction and validation from Authorization headers
- Flexible Configuration: Customizable signing keys, algorithms, and extraction methods
- Error Handling: Appropriate HTTP responses for invalid tokens
- Claims Access: Easy access to verified JWT claims
Pros and Cons
Pros
- Native and efficient integration with Echo framework
- Rapid response and updates to security vulnerabilities
- Lightweight and high-performance JWT authentication functionality
- Simple API with easy configuration and customization
- Idiomatic Go code style
- Maintains full compatibility with Echo v4
Cons
- Exclusive to Echo framework, cannot be used with other frameworks
- Increased dependencies due to being a separate package
- Potential type assertion issues between golang-jwt/jwt versions
- Functionality limited to basic JWT authentication
- Complex authentication flows require additional implementation
Reference Pages
- GitHub - labstack/echo-jwt
- Echo JWT Middleware Official Documentation
- Go Packages - echo-jwt/v4
- golang-jwt/jwt/v5
Code Examples
Basic Installation and Setup
# Install Echo JWT middleware
go get github.com/labstack/echo-jwt/v4
go get github.com/golang-jwt/jwt/v5
# Packages for Echo v4
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4/middleware
Basic JWT Authentication Setup
package main
import (
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type jwtCustomClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
var jwtSecret = []byte("your-secret-key")
func main() {
e := echo.New()
// Basic middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// Public routes
e.POST("/login", login)
e.POST("/register", register)
e.GET("/public", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Public endpoint",
})
})
// JWT protected route group
protected := e.Group("/api")
protected.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: jwtSecret,
NewClaimsFunc: func(c echo.Context) jwt.Claims {
return new(jwtCustomClaims)
},
}))
protected.GET("/profile", getProfile)
protected.PUT("/profile", updateProfile)
protected.GET("/admin", adminOnly)
e.Logger.Fatal(e.Start(":8080"))
}
func login(c echo.Context) error {
type LoginRequest struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
var req LoginRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request format",
})
}
// User authentication (use proper authentication logic in real implementation)
if req.Username != "admin" || req.Password != "password" {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Authentication failed",
})
}
// Generate JWT token
claims := &jwtCustomClaims{
UserID: 1,
Username: req.Username,
Role: "admin",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "echo-jwt-app",
Subject: "user-authentication",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtSecret)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": "Failed to generate token",
})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "Login successful",
"token": tokenString,
"user": map[string]interface{}{
"id": claims.UserID,
"username": claims.Username,
"role": claims.Role,
},
})
}
Protected Endpoint Implementation
func getProfile(c echo.Context) error {
// Get user information from JWT token
token, ok := c.Get("user").(*jwt.Token)
if !ok {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "JWT token not found",
})
}
claims, ok := token.Claims.(*jwtCustomClaims)
if !ok {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid claims format",
})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"user_id": claims.UserID,
"username": claims.Username,
"role": claims.Role,
"issued_at": claims.IssuedAt,
"expires_at": claims.ExpiresAt,
})
}
func updateProfile(c echo.Context) error {
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(*jwtCustomClaims)
type UpdateProfileRequest struct {
Email string `json:"email"`
FullName string `json:"full_name"`
}
var req UpdateProfileRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request format",
})
}
// Profile update logic (database operations, etc.)
// userService.UpdateProfile(claims.UserID, req)
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "Profile updated successfully",
"user_id": claims.UserID,
"updated_fields": req,
})
}
Role-Based Access Control (RBAC)
// Custom middleware: Role verification
func requireRole(allowedRoles ...string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(*jwtCustomClaims)
for _, role := range allowedRoles {
if claims.Role == role {
return next(c)
}
}
return c.JSON(http.StatusForbidden, map[string]string{
"error": "Access denied to this resource",
})
}
}
}
func adminOnly(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Admin-only endpoint",
})
}
// Usage in route groups
func setupRoutes(e *echo.Echo) {
// Admin-only routes
admin := e.Group("/admin")
admin.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: jwtSecret,
NewClaimsFunc: func(c echo.Context) jwt.Claims {
return new(jwtCustomClaims)
},
}))
admin.Use(requireRole("admin"))
admin.GET("/users", adminOnly)
admin.DELETE("/users/:id", deleteUser)
// Regular user routes
user := e.Group("/user")
user.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: jwtSecret,
NewClaimsFunc: func(c echo.Context) jwt.Claims {
return new(jwtCustomClaims)
},
}))
user.Use(requireRole("user", "admin"))
user.GET("/dashboard", userDashboard)
}
Advanced Configuration and Customization
func advancedJWTConfig() echojwt.Config {
return echojwt.Config{
// Signing key (recommended to get from environment variables)
SigningKey: []byte("your-secret-key"),
// Specify signing algorithm
SigningMethod: "HS256",
// Claims creation function
NewClaimsFunc: func(c echo.Context) jwt.Claims {
return new(jwtCustomClaims)
},
// Customize token extraction method
TokenLookup: "header:Authorization:Bearer ,cookie:token,query:token",
// Error handler for authentication failures
ErrorHandler: func(c echo.Context, err error) error {
return c.JSON(http.StatusUnauthorized, map[string]interface{}{
"error": "Authentication failed",
"details": err.Error(),
"timestamp": time.Now().Unix(),
})
},
// Before hook: processing before token validation
BeforeFunc: func(c echo.Context) {
// Request logging, metrics collection, etc.
c.Logger().Info("Starting JWT authentication")
},
// Success hook: processing after successful authentication
SuccessHandler: func(c echo.Context) {
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(*jwtCustomClaims)
c.Logger().Infof("User %s authenticated", claims.Username)
},
// KeyFunc: support for multiple signing keys
KeyFunc: func(t *jwt.Token) (interface{}, error) {
// Return appropriate key based on token header
if keyID, ok := t.Header["kid"].(string); ok {
return getKeyByID(keyID), nil
}
return jwtSecret, nil
},
}
}
func getKeyByID(keyID string) []byte {
// Return appropriate signing key based on key ID
keys := map[string][]byte{
"key1": []byte("secret-key-1"),
"key2": []byte("secret-key-2"),
}
if key, exists := keys[keyID]; exists {
return key
}
return jwtSecret // Default key
}
Refresh Token Implementation
type RefreshTokenClaims struct {
UserID int `json:"user_id"`
Type string `json:"type"` // "refresh"
jwt.RegisteredClaims
}
func generateTokenPair(userID int, username, role string) (string, string, error) {
// Access token (short-term)
accessClaims := &jwtCustomClaims{
UserID: userID,
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 15)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "echo-jwt-app",
},
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
accessTokenString, err := accessToken.SignedString(jwtSecret)
if err != nil {
return "", "", err
}
// Refresh token (long-term)
refreshClaims := &RefreshTokenClaims{
UserID: userID,
Type: "refresh",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)), // 1 week
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "echo-jwt-app",
},
}
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
refreshTokenString, err := refreshToken.SignedString(jwtSecret)
if err != nil {
return "", "", err
}
return accessTokenString, refreshTokenString, nil
}
func refreshToken(c echo.Context) error {
type RefreshRequest struct {
RefreshToken string `json:"refresh_token"`
}
var req RefreshRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request format",
})
}
// Validate refresh token
token, err := jwt.ParseWithClaims(req.RefreshToken, &RefreshTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid refresh token",
})
}
claims := token.Claims.(*RefreshTokenClaims)
if claims.Type != "refresh" {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid token type",
})
}
// Generate new access token
// Get user information (from database)
user := getUserByID(claims.UserID) // Implementation required
accessToken, _, err := generateTokenPair(user.ID, user.Username, user.Role)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": "Failed to generate token",
})
}
return c.JSON(http.StatusOK, map[string]string{
"access_token": accessToken,
"token_type": "Bearer",
})
}