ory/hydra · error

cookiex: payload must be a flat JSON object with string valu

Error message

cookiex: payload must be a flat JSON object with string values while legacy encode is enabled

What it means

When legacy encode mode is enabled, sealLegacy serializes the cookie payload and unmarshals it into map[string]string to feed the legacy securecookie codec. If the JSON round-trip fails (values are not all strings) or the map is nil, this error is returned because the legacy format can only encode flat string-valued objects.

Source

Thrown at oryx/cookiex/legacy_securecookie.go:125

// payload are coerced to empty strings by the bridge; do not use pointer-typed
// fields while legacy encode is enabled.
func WithLegacyEncode() Option {
	return func(c *config) { c.legacyEncode = true }
}

// sealLegacy bridges T through its JSON representation into the flat
// string-to-string map that the securecookie stores used.
func (c *Codec[T]) sealLegacy(name string, value T) (string, error) {
	buf, err := json.Marshal(value)
	if err != nil {
		return "", errors.Wrap(err, "cookiex: cannot marshal cookie value")
	}
	var flat map[string]string
	if err := json.Unmarshal(buf, &flat); err != nil {
		return "", errors.Wrap(err, "cookiex: payload must be a flat JSON object with string values while legacy encode is enabled")
	}
	if flat == nil {
		return "", errors.New("cookiex: payload must be a flat JSON object with string values while legacy encode is enabled")
	}
	values := make(map[any]any, len(flat))
	for k, v := range flat {
		values[k] = v
	}
	encoded, err := securecookie.EncodeMulti(name, values, c.legacy.codecs[0])
	if err != nil {
		return "", errors.Wrap(err, "cookiex: cannot encode legacy cookie")
	}
	return encoded, nil
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Convert all payload values to strings before Set: use strconv.Itoa/FormatBool or fmt.Sprint for each value.
  2. Store nested/structured data as a single JSON-encoded string value.
  3. If non-string values are required, disable WithLegacyEncode and use the modern codec path.
  4. Use errors.As/Is on the wrapped json.Unmarshal error to distinguish a malformed payload from the nil-map case.

Example fix

// before
vals := map[string]any{"count": 42}
c.Set(w, "sess", vals)
// after
vals := map[string]any{"count": strconv.Itoa(42)}
c.Set(w, "sess", vals)
Defensive patterns

Strategy: validation

Validate before calling

func stringifyValues(in map[string]any) (map[string]any, error) {
  out := make(map[string]any, len(in))
  for k, v := range in {
    s, ok := v.(string)
    if !ok { return nil, fmt.Errorf("cookie value %q is not a string", k) }
    out[k] = s
  }
  return out, nil
}
// apply before c.Set while legacy encode is enabled

Try / catch

if _, err := c.Set(w, name, vals); err != nil {
  var se *json.SyntaxError
  if errors.As(err, &se) || strings.Contains(err.Error(), "flat JSON object") {
    return fmt.Errorf("cookie payload has non-string values: %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: Calling Set (which delegates to sealLegacy) on a cookiex codec created with WithLegacyEncode(true), passing a values map whose JSON contains non-string values (numbers, booleans, nested objects, arrays) or when the payload unmarshals to a nil map.

Common situations: Migrating an app that previously stored ints/bools in session cookies (e.g. "count": 42) onto cookiex with legacy encode on; storing nested structures that the old encoder silently accepted via gob but this path rejects.

Related errors


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