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()
				return

View on GitHub (pinned to 71e5442fc5)

Solutions

  1. Re-mint the token with jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{...}) so claims are a JSON object (see MakeJWT in the same file)
  2. Inspect the token payload (decode the middle JWT segment) and confirm it is a JSON object like {"name":...,"exp":...}
  3. 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

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.