go-kit/kit · error · ErrUnexpectedSigningMethod

unexpected signing method

Error message

unexpected signing method

What it means

Returned from inside the keyfunc used by jwt.NewParser when token.Method != the signing method passed to NewParser (middleware.go:109-111). It then surfaces to the caller via the ValidationError.Inner path. Its security purpose is to stop algorithm confusion: the verifier must pin the expected algorithm rather than trust the token header.

Source

Thrown at auth/jwt/middleware.go:47

	// middleware's context.
	ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")

	// ErrTokenInvalid denotes a token was not able to be validated.
	ErrTokenInvalid = errors.New("JWT was invalid")

	// ErrTokenExpired denotes a token's expire header (exp) has since passed.
	ErrTokenExpired = errors.New("JWT is expired")

	// ErrTokenMalformed denotes a token was not formatted as a JWT.
	ErrTokenMalformed = errors.New("JWT is malformed")

	// ErrTokenNotActive denotes a token's not before header (nbf) is in the
	// future.
	ErrTokenNotActive = errors.New("token is not valid yet")

	// ErrUnexpectedSigningMethod denotes a token was signed with an unexpected
	// signing method.
	ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
)

// NewSigner creates a new JWT generating middleware, specifying key ID,
// signing string, signing method and the claims you would like it to contain.
// Tokens are signed with a Key ID header (kid) which is useful for determining
// the key to use for parsing. Particularly useful for clients.
func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			token := jwt.NewWithClaims(method, claims)
			token.Header["kid"] = kid

			// Sign and get the complete encoded token as a string using the secret
			tokenString, err := token.SignedString(key)
			if err != nil {
				return nil, err
			}
			ctx = context.WithValue(ctx, JWTContextKey, tokenString)

View on GitHub (pinned to 78fbbceece)

Solutions

  1. Align both sides: pass the SAME jwt.SigningMethod* constant to jwt.NewSigner and jwt.NewParser
  2. Never take the algorithm from the token header — always pin it in the parser configuration
  3. If multiple algorithms are legitimately in use, compare token.Method.Alg() against an allowlist inside your keyfunc
  4. Redeploy both signer and parser together when rotating algorithms

Example fix

// before: signer signs HMAC, parser pins RSA
signerMW := jwt.NewSigner("k1", hmacKey, jwt.SigningMethodHS256, claims)
parserMW := jwt.NewParser(kf, jwt.SigningMethodRS256, jwt.MapClaimsFactory)

// after: same method on both sides
parserMW := jwt.NewParser(kf, jwt.SigningMethodHS256, jwt.MapClaimsFactory)
Defensive patterns

Strategy: validation

Validate before calling

// at startup, assert signer and parser share one algorithm constant
var allowedAlgs = map[string]bool{"HS256": true, "RS256": true}
func parserConfigOK(method jwt.SigningMethod) bool {
	return allowedAlgs[method.Alg()]
}

Type guard

func isExpectedMethod(token *jwt.Token, want jwt.SigningMethod) bool {
	return token.Method.Alg() == want.Alg()
}

Try / catch

if _, err := ep(ctx, req); err != nil {
	if errors.Is(err, jwt.ErrUnexpectedSigningMethod) {
		// 401 + security alert: alg mismatch is a classic token-tampering signal — never auto-retry
	}
}

Prevention

When it happens

Trigger: Signer uses jwt.SigningMethodHS256 but NewParser was configured with jwt.SigningMethodRS256 (or vice versa); multi-service setups where one service signs with a different alg than the verifier expects; an attacker crafts a token with alg switched (e.g. to 'none' or HS vs RS confusion) and the pinned-method check fires; refactoring changed the method on one side only.

Common situations: Client and server auth config drift between deploys; example code copied with RS256 while the existing signer uses HMAC; rotating from HMAC to RSA keys but only one side deployed; keyfunc that fails to inspect the alg at all (the go-kit default check catches it here).

Related errors


AI-assisted analysis of go-kit/kit@78fbbceece (2026-08-15). Data as JSON: /api/errors/2817f8d971a0e44f. Report an issue: GitHub.