v2rayA/v2rayA · error
UNAUTHORIZED
UNAUTHORIZED
Error message
bad token: invalid claims
What it means
The JWT middleware in service/pkg/server/jwt/jwtTools.go (JWTAuth) parses the bearer token from the Authorization header (or Argument header fallback) and then type-asserts token.Claims to jwt.MapClaims. If the assertion fails, it responds 'bad token: invalid claims' with an unauthorized error and aborts the request. This happens when the token parses and verifies cryptographically but its claims are not a JSON object map (e.g. a token whose claims are a struct or non-object JSON value).
Source
Thrown at service/pkg/server/jwt/jwtTools.go:56
)
if err != nil {
if errors.Is(err, request.ErrNoTokenInRequest) {
token, err = request.ParseFromRequest(ctx.Request, AuthorizationArgumentExtractor,
func(token *jwt.Token) (interface{}, error) {
return getSecret(), nil
},
request.WithParser(parser),
)
}
if err != nil {
common.Response(ctx, common.UNAUTHORIZED, err.Error())
ctx.Abort()
return
}
}
mapClaims, ok := token.Claims.(jwt.MapClaims)
if !ok {
common.ResponseError(ctx, errors.New("bad token: invalid claims"))
ctx.Abort()
return
}
exp, err := mapClaims.GetExpirationTime()
if err == nil && exp != nil {
if time.Now().After(exp.Time) {
common.ResponseError(ctx, errors.New("expired token"))
ctx.Abort()
return
}
}
//如果需要Admin权限
if Admin {
adminVal, _ := mapClaims["admin"]
if adminVal != true {
common.ResponseError(ctx, errors.New("admin required"))
ctx.Abort()
returnView on GitHub (pinned to 71e5442fc5)
Solutions
- Re-mint the token with jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{...}) so claims are a JSON object (see MakeJWT in the same file)
- Inspect the token payload (decode the middle JWT segment) and confirm it is a JSON object like {"name":...,"exp":...}
- Ensure the client uses the token issued by this service's login endpoint rather than a token from another system
Example fix
// before: claims serialized as a non-map payload
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, myCustomStruct)
// after: always use MapClaims so the middleware's assertion succeeds
claims := jwt.MapClaims{"name": "alice", "exp": jwt.NewNumericDate(time.Now().Add(time.Hour))}
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) Defensive patterns
Strategy: type-guard
Validate before calling
func claimsAreMap(token *jwt.Token) bool {
_, ok := token.Claims.(jwt.MapClaims)
return ok
}
// decode payload before sending:
// parts := strings.Split(tokenString, "."); payload, _ := base64.RawURLEncoding.DecodeString(parts[1]); check payload starts with '{' Type guard
func hasMapClaims(tok *jwt.Token) (jwt.MapClaims, bool) {
claims, ok := tok.Claims.(jwt.MapClaims)
return claims, ok && tok.Valid
} Try / catch
token, err := request.ParseFromRequest(req, request.AuthorizationHeaderExtractor, keyFunc)
if err != nil {
// handle parse/signature errors
} else if claims, ok := token.Claims.(jwt.MapClaims); !ok {
// treat as unauthorized: re-authenticate to get a MapClaims-based token
} Prevention
- Always mint tokens with jwt.MapClaims, not custom claim types, when this middleware is in the path
- Spot-check token payload decodes to a JSON object before deploying a new issuer
- Keep token minting centralized in one helper (e.g. MakeJWT)
When it happens
Trigger: A request supplies a syntactically valid, correctly signed HS256 token whose claims payload is not a JSON object (e.g. the token body is a JSON array, string, or number), so token.Claims.(jwt.MapClaims) fails in the middleware at jwtTools.go:55.
Common situations: Tokens minted by a non-standard issuer or an older/different library that serializes claims as a non-map type; corrupted or hand-crafted tokens; mixing token formats between services after an API change.
Related errors
AI-assisted analysis of v2rayA/v2rayA@71e5442fc5 (2026-09-05).
Data as JSON: /api/errors/15c7496f568cdb2f.
Report an issue: GitHub.