Billionmail/BillionMail · error
unexpected signing method: %v
Error message
unexpected signing method: %v
What it means
jwt.Parse rejects the token because its 'alg' header is not an HMAC method (HS256/HS384/HS512). This repo's keyfunc intentionally returns an error when the signing method type-asserts to anything other than *jwt.SigningMethodHMAC, a standard protection against algorithm-confusion attacks (e.g. RS256/none). The wrapped message 'failed to parse JWT' is then produced by the caller.
Source
Thrown at core/internal/service/batch_mail/jwt.go:154
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(cfg.secret))
}
// ParseUnsubscribeJWT 解析退订JWT
func ParseUnsubscribeJWT(tokenString string) (*UnsubscribeClaims, error) {
if tokenString == "" {
return nil, errors.New("empty token string")
}
cfg := getConfig()
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(cfg.secret), nil
})
if err != nil {
return nil, fmt.Errorf("failed to parse JWT: %w", err)
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
result := &UnsubscribeClaims{}
// Extract email
if email, ok := claims["email"].(string); ok {
result.Email = email
} else {
return nil, errors.New("JWT missing email claim")
}
View on GitHub (pinned to fc36c76c05)
Solutions
- Sign the token with jwt.SigningMethodHS256 (or another HMAC variant) matching the secret in getConfig().secret
- Confirm the token producer uses the same batch_mail JWT flow (GenerateUnsubscribeJWT), not a different JWT issuer
- If asymmetric signing is genuinely needed, extend the keyfunc to accept that method and return the corresponding public key
Example fix
// before token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // after token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
Defensive patterns
Strategy: validation
Validate before calling
func isHMAC(token *jwt.Token) bool {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
return ok
}
// call jwt.Parse first and check err via errors.As
var algErr interface{ Error() string }
_ = algErr Type guard
func hasAlg(token *jwt.Token, want string) bool {
alg, ok := token.Header["alg"].(string)
return ok && alg == want
} Prevention
- Always sign with jwt.SigningMethodHS256 in this codebase
- Keep the HMAC-only keyfunc — do not loosen it without adding a key-per-method allowlist
- Add a unit test asserting tokens signed with RS256/none are rejected
- Share one token-generation helper so producer and verifier agree on alg
When it happens
Trigger: A unsubscribe JWT was signed with a non-HMAC algorithm (e.g. RS256, ES256, or 'none'), or crafted/externally generated tokens hit ParseUnsubscribeJWT with a foreign alg header.
Common situations: Tokens minted by a different service using asymmetric keys; an attacker probing the endpoint with alg=none; a library upgrade where the default signing method changed; mixing the subscribe-confirm token flow with the unsubscribe token flow.
Related errors
- failed to parse JWT: %w
- JWT missing email claim
- JWT has expired
- JWT missing or invalid group_id claim
- invalid token claims
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/2bdf2c993f5bbccf.
Report an issue: GitHub.