ory/hydra · error · ErrInvalidCookie

cookiex: cookie could not be decoded

Error message

cookiex: cookie could not be decoded

What it means

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.

Source

Thrown at oryx/cookiex/cookiex.go:46

	// formatPrefix marks the versioned wire format. A dot is not part of the
	// base64url alphabet, so legacy securecookie values can never collide
	// with the prefix.
	formatPrefix = "v1."
	// kdfInfo domain-separates the HKDF key derivation.
	kdfInfo = "ory/x/cookiex/v1"
	// aadPrefix domain-separates the additional authenticated data.
	aadPrefix = "ory/x/cookiex/v1"
	// defaultMaxAge matches the gorilla/securecookie default that the
	// previous cookie stores relied on.
	defaultMaxAge = 30 * 24 * time.Hour
	// maxCookieValueLength is the browser limit that securecookie also
	// enforced.
	maxCookieValueLength = 4096
)

// ErrInvalidCookie is returned when a cookie with the requested name is
// present but none of its values could be decoded (or none matched).
var ErrInvalidCookie = errors.New("cookiex: cookie could not be decoded")

type (
	// Option configures a Codec.
	Option func(*config)

	config struct {
		maxAge         time.Duration
		legacyKeyPairs [][]byte
		legacyEncode   bool
	}
)

// WithMaxAge overrides how old a cookie may be before decoding rejects it.
// The default is 30 days; zero disables the check. The value must not be
// negative.
func WithMaxAge(d time.Duration) Option {
	return func(c *config) { c.maxAge = d }
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Keep old secrets in the `secrets` slice (prepend new ones) so previously sealed cookies still verify.
  2. Have the caller treat ErrInvalidCookie as 'anonymous/no session' (log and continue) rather than a hard failure — clear the bad cookie.
  3. Ensure the same `purpose` string is used for sealing and opening.
  4. Check middleware/proxies for cookie truncation or re-encoding, and keep values under 4096 bytes.

Example fix

// before
val, err := codec.GetMatching(r, "session")
if err != nil { http.Error(w, "server error", 500) }
// after
val, err := codec.GetMatching(r, "session")
if errors.Is(err, cookiex.ErrInvalidCookie) {
    http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1}) // clear bad cookie
    val = zeroValue // treat as unauthenticated
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check the raw cookie before decoding
func cookieLooksPlausible(c *http.Cookie) bool {
    return c != nil && c.Value != "" && len(c.Value) <= 4096
}

Type guard

func isErrInvalidCookie(err error) bool {
    return errors.Is(err, cookiex.ErrInvalidCookie)
}

Try / catch

val, err := codec.GetMatching(r, "session")
switch {
case errors.Is(err, cookiex.ErrInvalidCookie):
    log.Warn("undecodable cookie; clearing and continuing as anonymous")
    http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1})
    val = cookiexZeroValue
case err != nil:
    http.Error(w, "internal error", http.StatusInternalServerError)
    return
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/e8250579603e26b8. Report an issue: GitHub.