Skip to main content

Overview

The JWT authentication middleware provides token-based authentication for Kratos services. It validates JWT tokens on the server side and automatically adds tokens to requests on the client side.

Installation

go get github.com/go-kratos/kratos/v2/middleware/auth/jwt

Server Middleware

The Server function creates a server-side JWT authentication middleware that validates tokens from incoming requests:
func Server(keyFunc jwt.Keyfunc, opts ...Option) middleware.Middleware

Basic Usage

import (
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/golang-jwt/jwt/v5"
)

var (
    // Secret key for signing tokens
    secretKey = []byte("your-secret-key")
)

func main() {
    // Create HTTP server with JWT authentication
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            jwt.Server(
                func(token *jwt.Token) (interface{}, error) {
                    return secretKey, nil
                },
            ),
        ),
    )
    
    app := kratos.New(
        kratos.Server(httpSrv),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Token Validation

The middleware expects tokens in the Authorization header:
Authorization: Bearer <token>
Example HTTP request:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  http://localhost:8000/api/user

Client Middleware

The Client function creates a client-side middleware that automatically adds JWT tokens to outgoing requests:
func Client(keyProvider jwt.Keyfunc, opts ...Option) middleware.Middleware

Usage Example

import (
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/golang-jwt/jwt/v5"
)

// Create HTTP client with JWT
conn, err := http.NewClient(
    context.Background(),
    http.WithEndpoint("127.0.0.1:8000"),
    http.WithMiddleware(
        jwt.Client(
            func(token *jwt.Token) (interface{}, error) {
                return []byte("your-secret-key"), nil
            },
        ),
    ),
)

Configuration Options

WithSigningMethod

Specify the signing algorithm (default: HS256):
import "github.com/golang-jwt/jwt/v5"

jwt.Server(
    keyFunc,
    jwt.WithSigningMethod(jwt.SigningMethodHS512),
)
Supported signing methods:
  • jwt.SigningMethodHS256 - HMAC with SHA-256 (default)
  • jwt.SigningMethodHS384 - HMAC with SHA-384
  • jwt.SigningMethodHS512 - HMAC with SHA-512
  • jwt.SigningMethodRS256 - RSA with SHA-256
  • jwt.SigningMethodRS384 - RSA with SHA-384
  • jwt.SigningMethodRS512 - RSA with SHA-512
  • jwt.SigningMethodES256 - ECDSA with SHA-256
  • jwt.SigningMethodES384 - ECDSA with SHA-384
  • jwt.SigningMethodES512 - ECDSA with SHA-512

WithClaims

Use custom claims structure:
import "github.com/golang-jwt/jwt/v5"

// Define custom claims
type CustomClaims struct {
    UserID string `json:"user_id"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

// Server: return new instance each time to avoid concurrent writes
jwt.Server(
    keyFunc,
    jwt.WithClaims(func() jwt.Claims {
        return &CustomClaims{}
    }),
)

// Client: can return same instance for performance
var claims = &CustomClaims{
    UserID: "user123",
    Role:   "admin",
}

jwt.Client(
    keyFunc,
    jwt.WithClaims(func() jwt.Claims {
        return claims
    }),
)
When using custom claims on the server side, the claims function must return a new instance each time to avoid concurrent write issues.

WithTokenHeader

Add custom headers to the JWT token (client only):
jwt.Client(
    keyFunc,
    jwt.WithTokenHeader(map[string]any{
        "kid": "key-id-123",
        "typ": "JWT",
    }),
)

Key Function

The key function provides the key for signing or verifying tokens:
type Keyfunc func(*jwt.Token) (interface{}, error)

HMAC Key Function

func keyFunc(token *jwt.Token) (interface{}, error) {
    return []byte("your-secret-key"), nil
}

RSA Key Function

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "os"
)

func rsaKeyFunc(token *jwt.Token) (interface{}, error) {
    // Read public key
    keyData, err := os.ReadFile("public_key.pem")
    if err != nil {
        return nil, err
    }
    
    block, _ := pem.Decode(keyData)
    if block == nil {
        return nil, errors.New("failed to parse PEM block")
    }
    
    pub, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return nil, err
    }
    
    return pub.(*rsa.PublicKey), nil
}

jwt.Server(rsaKeyFunc)

Dynamic Key Function

func dynamicKeyFunc(token *jwt.Token) (interface{}, error) {
    // Get key ID from token header
    kid, ok := token.Header["kid"].(string)
    if !ok {
        return nil, errors.New("missing key ID")
    }
    
    // Look up key by ID
    key, err := keyStore.GetKey(kid)
    if err != nil {
        return nil, err
    }
    
    return key, nil
}

Extracting Claims

Use FromContext to extract claims from the context:
import (
    "context"
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    jwtLib "github.com/golang-jwt/jwt/v5"
)

func (s *service) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    // Extract claims from context
    claims, ok := jwt.FromContext(ctx)
    if !ok {
        return nil, errors.Unauthorized("UNAUTHORIZED", "missing claims")
    }
    
    // Use standard claims
    if mapClaims, ok := claims.(jwtLib.MapClaims); ok {
        userID := mapClaims["user_id"].(string)
        role := mapClaims["role"].(string)
        // Use userID and role
    }
    
    // Or use custom claims
    if customClaims, ok := claims.(*CustomClaims); ok {
        userID := customClaims.UserID
        role := customClaims.Role
        // Use userID and role
    }
    
    return &pb.User{}, nil
}

Error Handling

The middleware returns specific errors for different failure scenarios:
var (
    ErrMissingJwtToken        = errors.Unauthorized(reason, "JWT token is missing")
    ErrMissingKeyFunc         = errors.Unauthorized(reason, "keyFunc is missing")
    ErrTokenInvalid           = errors.Unauthorized(reason, "Token is invalid")
    ErrTokenExpired           = errors.Unauthorized(reason, "JWT token has expired")
    ErrTokenParseFail         = errors.Unauthorized(reason, "Fail to parse JWT token")
    ErrUnSupportSigningMethod = errors.Unauthorized(reason, "Wrong signing method")
    ErrWrongContext           = errors.Unauthorized(reason, "Wrong context for middleware")
    ErrNeedTokenProvider      = errors.Unauthorized(reason, "Token provider is missing")
    ErrSignToken              = errors.Unauthorized(reason, "Can not sign token")
    ErrGetKey                 = errors.Unauthorized(reason, "Can not get key while signing token")
)

Complete Example

package main

import (
    "context"
    "log"
    "time"
    
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    "github.com/go-kratos/kratos/v2/middleware/selector"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/golang-jwt/jwt/v5"
)

var (
    secretKey = []byte("my-secret-key-change-in-production")
)

// Custom claims
type CustomClaims struct {
    UserID string `json:"user_id"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

// Key function
func keyFunc(token *jwt.Token) (interface{}, error) {
    return secretKey, nil
}

// Generate a token (for testing)
func generateToken(userID, role string) (string, error) {
    claims := CustomClaims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
            Issuer:    "my-service",
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secretKey)
}

func main() {
    // Create HTTP server with JWT on specific routes
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            // Apply JWT only to /api/* routes
            selector.Server(
                jwt.Server(
                    keyFunc,
                    jwt.WithSigningMethod(jwt.SigningMethodHS256),
                    jwt.WithClaims(func() jwt.Claims {
                        return &CustomClaims{}
                    }),
                ),
            ).Prefix("/api").Build(),
        ),
    )
    
    app := kratos.New(
        kratos.Name("jwt-example"),
        kratos.Server(httpSrv),
    )
    
    // Generate test token
    token, _ := generateToken("user123", "admin")
    log.Printf("Test token: %s", token)
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Selective Authentication

Use the selector middleware to apply JWT only to specific routes:
import (
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    "github.com/go-kratos/kratos/v2/middleware/selector"
)

// Apply JWT to specific paths
http.Middleware(
    selector.Server(
        jwt.Server(keyFunc),
    ).Path(
        "/api.v1.UserService/GetUser",
        "/api.v1.UserService/UpdateUser",
    ).Build(),
)

// Apply JWT to all /admin routes
http.Middleware(
    selector.Server(
        jwt.Server(keyFunc),
    ).Prefix("/admin").Build(),
)

// Exclude health check
http.Middleware(
    selector.Server(
        jwt.Server(keyFunc),
    ).Match(func(ctx context.Context, operation string) bool {
        return operation != "/healthz"
    }).Build(),
)

Best Practices

Always use strong, randomly generated secret keys. Never hardcode keys in source code.
// Good: Load from environment or secret manager
secretKey := []byte(os.Getenv("JWT_SECRET_KEY"))

// Bad: Hardcoded key
secretKey := []byte("my-secret")
Always set appropriate expiration times for tokens. Use short-lived access tokens (15-60 minutes) and longer-lived refresh tokens.
claims := jwt.RegisteredClaims{
    ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
}
Consider using RSA or ECDSA signing methods in production instead of HMAC for better security.
jwt.WithSigningMethod(jwt.SigningMethodRS256)
Always validate claims after extraction and check for required fields.
claims, ok := jwt.FromContext(ctx)
if !ok || claims == nil {
    return errors.Unauthorized("UNAUTHORIZED", "invalid token")
}
Apply JWT middleware only to routes that require authentication. Leave health checks and public endpoints unprotected.
Implement a token refresh mechanism to provide better user experience without compromising security.

Source Reference

The JWT middleware implementation can be found in:
  • middleware/auth/jwt/jwt.go:78 - Server middleware
  • middleware/auth/jwt/jwt.go:131 - Client middleware
  • middleware/auth/jwt/jwt.go:168 - NewContext function
  • middleware/auth/jwt/jwt.go:174 - FromContext function

Next Steps

Rate Limiting

Add rate limiting to protect your APIs

Validation

Validate request data

Build docs developers (and LLMs) love