ory/hydra · error

err.Error()

Error message

err.Error()

What it means

In fosite's WriteAccessResponse, if json.Marshal(responder.ToMap()) fails, the raw err.Error() text is written with status 500. ToMap flattens the access response (access_token, token_type, expires_in, scope, plus any session/extra fields); marshal failure means one of those values cannot be serialized — essentially always a custom session or extra-claims value containing unsupported types.

Source

Thrown at fosite/access_write.go:18

// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package fosite

import (
	"context"
	"encoding/json"
	"net/http"
)

func (f *Fosite) WriteAccessResponse(ctx context.Context, rw http.ResponseWriter, requester AccessRequester, responder AccessResponder) {
	rw.Header().Set("Cache-Control", "no-store")
	rw.Header().Set("Pragma", "no-cache")

	js, err := json.Marshal(responder.ToMap())
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	rw.Header().Set("Content-Type", "application/json;charset=UTF-8")

	rw.WriteHeader(http.StatusOK)
	_, _ = rw.Write(js)
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Audit your custom Session/claims types: remove channels, funcs, mutexes, contexts, and cyclic references; expose only JSON-safe fields with proper tags.
  2. Implement/fix MarshalJSON on custom claim types so they serialize deterministically.
  3. Write a unit test marshaling your session type (like TestWriteAccessResponse) to catch this before production.
  4. Handle the 500 by returning a generic oauth server_error to clients instead of leaking err.Error() — wrap WriteAccessResponse in middleware.

Example fix

// before
type Session struct {
	Ctx context.Context `json:"ctx"` // unserializable
	mu  sync.Mutex
}
// after
type Session struct {
	UserID string `json:"sub"`
	Expiry time.Time `json:"exp"`
}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := json.Marshal(session.ToMap()); err != nil {
	return fmt.Errorf("session not serializable: %w", err)
}

Type guard

func serializableSession(s fosite.Session) bool {
	_, err := json.Marshal(s)
	return err == nil
}

Try / catch

// Guard around WriteAccessResponse in your token handler:
func safeWriteAccess(ctx context.Context, rw http.ResponseWriter, req fosite.AccessRequester, res fosite.AccessResponder) {
	defer func() {
		if rec := recover(); rec != nil { log.Printf("access write panic: %v", rec) }
	}()
	if js, err := json.Marshal(res.ToMap()); err != nil {
		log.Printf("access response marshal failed: %v", err)
		fosite.WriteRFC6749Error(rw, fosite.ErrServerError, true)
		return
	}
	fosite.WriteAccessResponse(ctx, rw, req, res)
}

Prevention

When it happens

Trigger: json.Marshal(responder.ToMap()) fails in fosite/access_write.go:18 — e.g. a custom AccessRequester/Session whose GetSession or extra map exposes channels, funcs, cyclic pointers, or a custom type with an erroring MarshalJSON. Reached from token endpoint handlers and exercised by TestWriteAccessResponse.

Common situations: Custom session structs storing context.Context, sync primitives (sync.Mutex), or time.Time alternatives with broken MarshalJSON; storing request-scoped closures in extra claims; injecting non-JSON types into IDToken claims that propagate into the access response map.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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