{"record":{"id":"e8250579603e26b8","repo":"ory/hydra","slug":"cookiex-cookie-could-not-be-decoded","errorCode":null,"errorMessage":"cookiex: cookie could not be decoded","messagePattern":"cookiex: cookie could not be decoded","errorType":"validation","errorClass":"ErrInvalidCookie","httpStatus":null,"severity":"error","filePath":"oryx/cookiex/cookiex.go","lineNumber":46,"sourceCode":"\t// formatPrefix marks the versioned wire format. A dot is not part of the\n\t// base64url alphabet, so legacy securecookie values can never collide\n\t// with the prefix.\n\tformatPrefix = \"v1.\"\n\t// kdfInfo domain-separates the HKDF key derivation.\n\tkdfInfo = \"ory/x/cookiex/v1\"\n\t// aadPrefix domain-separates the additional authenticated data.\n\taadPrefix = \"ory/x/cookiex/v1\"\n\t// defaultMaxAge matches the gorilla/securecookie default that the\n\t// previous cookie stores relied on.\n\tdefaultMaxAge = 30 * 24 * time.Hour\n\t// maxCookieValueLength is the browser limit that securecookie also\n\t// enforced.\n\tmaxCookieValueLength = 4096\n)\n\n// ErrInvalidCookie is returned when a cookie with the requested name is\n// present but none of its values could be decoded (or none matched).\nvar ErrInvalidCookie = errors.New(\"cookiex: cookie could not be decoded\")\n\ntype (\n\t// Option configures a Codec.\n\tOption func(*config)\n\n\tconfig struct {\n\t\tmaxAge         time.Duration\n\t\tlegacyKeyPairs [][]byte\n\t\tlegacyEncode   bool\n\t}\n)\n\n// WithMaxAge overrides how old a cookie may be before decoding rejects it.\n// The default is 30 days; zero disables the check. The value must not be\n// negative.\nfunc WithMaxAge(d time.Duration) Option {\n\treturn func(c *config) { c.maxAge = d }\n}","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/cookiex/cookiex.go#L28-L64","documentation":"cookiex's ErrInvalidCookie is returned when a cookie with the requested name is present but none of its values could be decoded or matched a known key — base64 decoding fails, no secret matches the MAC, the plaintext JSON is not the expected envelope, or it failed other decode steps. It wraps the raw decode errors with errors.WithStack, so callers should use errors.As to identify it.","triggerScenarios":"Codec.GetMatching/open returns ErrInvalidCookie when base64.RawURLEncoding decoding of the cookie value fails, or during open when no configured key verifies the value, or json.Unmarshal of the decrypted plaintext into the envelope fails (cookiex.go:158/174).","commonSituations":"Cookie written by an older/other app version with a different format or purpose string; secrets rotated so the old signing key is no longer in the key list; cookie truncated by proxies or the 4096-byte browser limit; cookie manually edited or corrupted; switching cookie codec (e.g. from legacy openLegacy format) without migration.","solutions":["Keep old secrets in the `secrets` slice (prepend new ones) so previously sealed cookies still verify.","Have the caller treat ErrInvalidCookie as 'anonymous/no session' (log and continue) rather than a hard failure — clear the bad cookie.","Ensure the same `purpose` string is used for sealing and opening.","Check middleware/proxies for cookie truncation or re-encoding, and keep values under 4096 bytes."],"exampleFix":"// before\nval, err := codec.GetMatching(r, \"session\")\nif err != nil { http.Error(w, \"server error\", 500) }\n// after\nval, err := codec.GetMatching(r, \"session\")\nif errors.Is(err, cookiex.ErrInvalidCookie) {\n    http.SetCookie(w, &http.Cookie{Name: \"session\", MaxAge: -1}) // clear bad cookie\n    val = zeroValue // treat as unauthenticated\n}","handlingStrategy":"try-catch","validationCode":"// pre-check the raw cookie before decoding\nfunc cookieLooksPlausible(c *http.Cookie) bool {\n    return c != nil && c.Value != \"\" && len(c.Value) <= 4096\n}","typeGuard":"func isErrInvalidCookie(err error) bool {\n    return errors.Is(err, cookiex.ErrInvalidCookie)\n}","tryCatchPattern":"val, err := codec.GetMatching(r, \"session\")\nswitch {\ncase errors.Is(err, cookiex.ErrInvalidCookie):\n    log.Warn(\"undecodable cookie; clearing and continuing as anonymous\")\n    http.SetCookie(w, &http.Cookie{Name: \"session\", MaxAge: -1})\n    val = cookiexZeroValue\ncase err != nil:\n    http.Error(w, \"internal error\", http.StatusInternalServerError)\n    return\n}","preventionTips":["Keep old secrets in the secrets list when rotating (prepend new)","Use the same purpose string for sealing and opening","Watch for proxies/load balancers truncating large cookies","On format migrations, keep the legacy open path available during rollout"],"tags":["cookies","go","session","encoding"],"backgroundTag":"cookie-decode-failed","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}