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.
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
}
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" )
}
Use Selective Authentication
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