{"record":{"id":"2817f8d971a0e44f","repo":"go-kit/kit","slug":"unexpected-signing-method","errorCode":null,"errorMessage":"unexpected signing method","messagePattern":"unexpected signing method","errorType":"exception","errorClass":"ErrUnexpectedSigningMethod","httpStatus":401,"severity":"error","filePath":"auth/jwt/middleware.go","lineNumber":47,"sourceCode":"\t// middleware's context.\n\tErrTokenContextMissing = errors.New(\"token up for parsing was not passed through the context\")\n\n\t// ErrTokenInvalid denotes a token was not able to be validated.\n\tErrTokenInvalid = errors.New(\"JWT was invalid\")\n\n\t// ErrTokenExpired denotes a token's expire header (exp) has since passed.\n\tErrTokenExpired = errors.New(\"JWT is expired\")\n\n\t// ErrTokenMalformed denotes a token was not formatted as a JWT.\n\tErrTokenMalformed = errors.New(\"JWT is malformed\")\n\n\t// ErrTokenNotActive denotes a token's not before header (nbf) is in the\n\t// future.\n\tErrTokenNotActive = errors.New(\"token is not valid yet\")\n\n\t// ErrUnexpectedSigningMethod denotes a token was signed with an unexpected\n\t// signing method.\n\tErrUnexpectedSigningMethod = errors.New(\"unexpected signing method\")\n)\n\n// NewSigner creates a new JWT generating middleware, specifying key ID,\n// signing string, signing method and the claims you would like it to contain.\n// Tokens are signed with a Key ID header (kid) which is useful for determining\n// the key to use for parsing. Particularly useful for clients.\nfunc NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware {\n\treturn func(next endpoint.Endpoint) endpoint.Endpoint {\n\t\treturn func(ctx context.Context, request interface{}) (response interface{}, err error) {\n\t\t\ttoken := jwt.NewWithClaims(method, claims)\n\t\t\ttoken.Header[\"kid\"] = kid\n\n\t\t\t// Sign and get the complete encoded token as a string using the secret\n\t\t\ttokenString, err := token.SignedString(key)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tctx = context.WithValue(ctx, JWTContextKey, tokenString)","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/go-kit/kit/blob/78fbbceece7bbcf073bee814a7772f4397ea756c/auth/jwt/middleware.go#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Align both sides: pass the SAME jwt.SigningMethod* constant to jwt.NewSigner and jwt.NewParser","Never take the algorithm from the token header — always pin it in the parser configuration","If multiple algorithms are legitimately in use, compare token.Method.Alg() against an allowlist inside your keyfunc","Redeploy both signer and parser together when rotating algorithms"],"exampleFix":"// before: signer signs HMAC, parser pins RSA\nsignerMW := jwt.NewSigner(\"k1\", hmacKey, jwt.SigningMethodHS256, claims)\nparserMW := jwt.NewParser(kf, jwt.SigningMethodRS256, jwt.MapClaimsFactory)\n\n// after: same method on both sides\nparserMW := jwt.NewParser(kf, jwt.SigningMethodHS256, jwt.MapClaimsFactory)","handlingStrategy":"validation","validationCode":"// at startup, assert signer and parser share one algorithm constant\nvar allowedAlgs = map[string]bool{\"HS256\": true, \"RS256\": true}\nfunc parserConfigOK(method jwt.SigningMethod) bool {\n\treturn allowedAlgs[method.Alg()]\n}","typeGuard":"func isExpectedMethod(token *jwt.Token, want jwt.SigningMethod) bool {\n\treturn token.Method.Alg() == want.Alg()\n}","tryCatchPattern":"if _, err := ep(ctx, req); err != nil {\n\tif errors.Is(err, jwt.ErrUnexpectedSigningMethod) {\n\t\t// 401 + security alert: alg mismatch is a classic token-tampering signal — never auto-retry\n\t}\n}","preventionTips":["Define the signing method once in shared config and pass it to both NewSigner and NewParser","Never derive the verification algorithm from the untrusted token header","Deploy algorithm changes atomically across signer and verifier"],"tags":["go","go-kit","jwt","authentication","signing-method","security"],"backgroundTag":null,"analyzedSha":"78fbbceece7bbcf073bee814a7772f4397ea756c","analyzedAt":"2026-08-15T22:31:35.570Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}