Billionmail/BillionMail · error
invalid token claims
Error message
invalid token claims
What it means
Fallback error: jwt.Parse succeeded but the top-level type assertion token.Claims.(jwt.MapClaims) failed or token.Valid is false, so the structured claim-extraction branch never ran. Rare in practice — jwt.Parse with no explicit claims type yields MapClaims, so this mostly fires on parser misconfiguration or token.Valid being false despite nil error.
Source
Thrown at core/internal/service/batch_mail/jwt.go:202
if exp, ok := claims["exp"].(float64); ok {
// Check if token has expired
if time.Now().Unix() > int64(exp) {
return nil, errors.New("JWT has expired")
}
result.RegisteredClaims.ExpiresAt = jwt.NewNumericDate(time.Unix(int64(exp), 0))
}
// Extract group ID
if groupID, ok := claims["group_id"].(float64); ok {
result.GroupId = int(groupID)
} else {
return nil, errors.New("JWT missing or invalid group_id claim")
}
g.Log().Debug(context.Background(), "JWT parsed successfully: %+v", result)
return result, nil
}
return nil, errors.New("invalid token claims")
}
type SubscribeConfirmClaims struct {
Email string `json:"email"`
GroupToken string `json:"group_token"`
jwt.RegisteredClaims
}
func getSubscribeConfirmConfig() *jwtConfig {
once.Do(func() {
config = loadSubscribeConfirmConfig()
})
return config
}
func loadSubscribeConfirmConfig() *jwtConfig {
ctx := gctx.New()
return &jwtConfig{View on GitHub (pinned to fc36c76c05)
Solutions
- Keep using plain jwt.Parse (default MapClaims) or change the assertion to match the configured claims type
- Log token.Valid and the concrete claims type when debugging
- If migrating to typed claims, port the email/group_id extraction into the struct's UnmarshalJSON
Example fix
// before
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
// after
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { Defensive patterns
Strategy: type-guard
Validate before calling
// ensure default MapClaims path: use jwt.Parse, not a custom-claims parser token, err := jwt.Parse(tokenString, keyfunc) // claims type stays jwt.MapClaims
Type guard
func validMapClaims(token *jwt.Token) (jwt.MapClaims, bool) {
c, ok := token.Claims.(jwt.MapClaims)
return c, ok && token.Valid
} Try / catch
claims, err := ParseUnsubscribeJWT(tok)
if err != nil {
if strings.Contains(err.Error(), "invalid token claims") {
log.Printf("claims assertion failed: token=%T valid=%v", nil, false)
}
return err
} Prevention
- Do not swap jwt.Parse for jwt.NewParser with a typed claims target without updating the assertion
- Add a regression test for the full happy-path parse
- Keep token.Valid checked together with the claims type assertion
- Review golang-jwt migration notes (v4→v5) when upgrading
When it happens
Trigger: A jwt.Parser configured with a different claims type (e.g. custom Claims struct) instead of default MapClaims; token.Valid false from a non-standard validation setup; future refactors changing the Parse call.
Common situations: Refactoring that swaps jwt.Parse for jwt.NewParser with ValidityChecks or a typed claims target; library-version migrations (golang-jwt v4/v5 differences in Valid semantics).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- JWT missing email claim
- JWT missing or invalid group_id claim
- unexpected signing method: %v
- failed to parse JWT: %w
- JWT has expired
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/9d953b199c447ee1.
Report an issue: GitHub.