ory/hydra · error

cookiex: legacy encode requires legacy key pairs

Error message

cookiex: legacy encode requires legacy key pairs

What it means

cookiex.New requires that enabling legacy encoding (WithLegacyEncode) is accompanied by at least one legacy key pair (WithLegacyKeyPairs), because the legacy securecookie codec cannot sign/encrypt without keys. New returns this error when legacyEncode is true but legacyKeyPairs is empty.

Source

Thrown at oryx/cookiex/cookiex.go:98

// character. The codec seals with a key derived from the first secret
// and opens with keys derived from any of them, so secrets rotate by
// prepending a new one.
func New[T any](purpose string, secrets [][]byte, opts ...Option) (*Codec[T], error) {
	if purpose == "" || strings.Contains(purpose, "|") {
		return nil, errors.New("cookiex: purpose must be non-empty and must not contain a pipe character")
	}
	if len(secrets) == 0 {
		return nil, errors.New("cookiex: at least one secret is required")
	}
	cfg := config{maxAge: defaultMaxAge}
	for _, opt := range opts {
		opt(&cfg)
	}
	if cfg.maxAge < 0 {
		return nil, errors.New("cookiex: max age must not be negative")
	}
	if cfg.legacyEncode && len(cfg.legacyKeyPairs) == 0 {
		return nil, errors.New("cookiex: legacy encode requires legacy key pairs")
	}
	keys := make([][32]byte, len(secrets))
	for i, secret := range secrets {
		key, err := hkdf.Key(sha256.New, secret, nil, kdfInfo, 32)
		if err != nil {
			return nil, errors.Wrap(err, "cookiex: cannot derive key")
		}
		keys[i] = [32]byte(key)
	}
	return &Codec[T]{
		purpose: purpose,
		keys:    keys,
		maxAge:  cfg.maxAge,
		legacy:  newLegacyState(cfg, cfg.maxAge),
		now:     time.Now,
	}, nil
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Pass at least one key pair via WithLegacyKeyPairs, e.g. cookiex.WithLegacyKeyPairs([][]byte{oldHashKey, oldBlockKey}).
  2. If legacy encoding is not needed, remove the WithLegacyEncode option.
  3. Load the legacy key pairs from config at startup and log/fail if they are empty before calling New.

Example fix

// before
cc, err := cookiex.New([][]byte{secret}, cookiex.WithLegacyEncode(true))
// after
cc, err := cookiex.New([][]byte{secret},
  cookiex.WithLegacyEncode(true),
  cookiex.WithLegacyKeyPairs([][]byte{hashKey, blockKey}))
Defensive patterns

Strategy: validation

Validate before calling

if len(legacyKeyPairs) == 0 {
  return errors.New("legacy encode requested but no legacy key pairs configured")
}
cc, err := cookiex.New(secrets,
  cookiex.WithLegacyEncode(true),
  cookiex.WithLegacyKeyPairs(legacyKeyPairs))

Try / catch

cfg, err := cookiex.New(secrets, opts...)
if err != nil {
  log.Fatalf("cookiex configuration invalid: %v", err)
}

Prevention

When it happens

Trigger: Calling cookiex.New(secrets...) with cookiex.WithLegacyEncode(true) (or equivalent) and no WithLegacyKeyPairs(...) option, so cfg.legacyKeyPairs has length 0.

Common situations: Migrating from the old securecookie-based format and enabling legacy encoding without copying over the previous key pairs; loading key pairs from env/config that came back empty; conditional wiring where the legacy option is set but the key-pair option is skipped.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


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