{"record":{"id":"31382d40f64ace6e","repo":"go-kit/kit","slug":"jwt-was-invalid","errorCode":null,"errorMessage":"JWT was invalid","messagePattern":"JWT was invalid","errorType":"exception","errorClass":"ErrTokenInvalid","httpStatus":401,"severity":"error","filePath":"auth/jwt/middleware.go","lineNumber":33,"sourceCode":"\tJWTContextKey contextKey = \"JWTToken\"\n\n\t// JWTTokenContextKey is an alias for JWTContextKey.\n\t//\n\t// Deprecated: prefer JWTContextKey.\n\tJWTTokenContextKey = JWTContextKey\n\n\t// JWTClaimsContextKey holds the key used to store the JWT Claims in the\n\t// context.\n\tJWTClaimsContextKey contextKey = \"JWTClaims\"\n)\n\nvar (\n\t// ErrTokenContextMissing denotes a token was not passed into the parsing\n\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.","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/go-kit/kit/blob/78fbbceece7bbcf073bee814a7772f4397ea756c/auth/jwt/middleware.go#L15-L51","documentation":"Returned by jwt.NewParser when jwt.ParseWithClaims succeeded without error but token.Valid is still false. It is the generic 'validation failed' outcome: the token was syntactically parseable and not expired/malformed/not-yet-active, but the signature or claims check rejected it without a more specific ValidationError bit being set.","triggerScenarios":"The keyfunc returned a different key than the one used to sign (wrong secret, wrong public key, wrong kid resolution); claims-based validation (issuer, subject, audience via the v4 validator) failed without the malformed/expired/nbf bits; parser and signer disagree on signing method family so the signature check fails; token was tampered with so the signature simply doesn't verify.","commonSituations":"Rotating secrets where old tokens are verified with the new key; multi-environment setups sharing tokens across envs with different keys; keyfunc that ignores the kid header and always returns one key; subtle claim mismatches (iss URL with/without trailing slash) after an identity-provider change.","solutions":["Confirm the keyfunc returns exactly the key the signer used — log the kid header and compare","Check issuer/audience/subject claim values against what the identity provider actually emits","Verify both sides use the same signing method in NewSigner and NewParser","Print the underlying jwt.ValidationError bits in a debug build to see which check failed before go-kit collapses it to ErrTokenInvalid"],"exampleFix":"// before: keyfunc ignores kid and always returns the current key\nkf := func(*jwt.Token) (interface{}, error) { return []byte(\"new-secret\"), nil }\n\n// after: resolve the key by kid so old and new tokens both verify\nvar keys = map[string][]byte{\"k1\": []byte(\"old-secret\"), \"k2\": []byte(\"new-secret\")}\nkf := func(t *jwt.Token) (interface{}, error) {\n\tkid, _ := t.Header[\"kid\"].(string)\n\tif k, ok := keys[kid]; ok {\n\t\treturn k, nil\n\t}\n\treturn nil, errors.New(\"unknown kid\")\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"resp, err := ep(ctx, req)\nif err != nil {\n\tswitch {\n\tcase errors.Is(err, jwt.ErrTokenInvalid):\n\t\t// 401: signature/claims failed — force re-authentication, do not blind-retry\n\tcase errors.Is(err, jwt.ErrTokenExpired):\n\t\t// 401: refresh token then retry once\n\t}\n}","preventionTips":["Resolve verification keys by the kid header so multi-key rotations verify correctly","Keep a single source of truth for issuer/audience values shared by signer and parser","Log the raw jwt.ValidationError bitfield at debug level before go-kit flattens it to ErrTokenInvalid"],"tags":["go","go-kit","jwt","authentication","signature","key-management"],"backgroundTag":null,"analyzedSha":"78fbbceece7bbcf073bee814a7772f4397ea756c","analyzedAt":"2026-08-15T22:31:35.570Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}