ory/hydra · error

server_error

server_error

Error message

{"error":"server_error","error_description":"%s"}

What it means

In fosite's WriteAuthorizeError, when the error is not meant to redirect back to the client (e.g. invalid client or no redirect URI registered), fosite writes the error as a JSON body. If json.Marshal(rfcerr) then fails, and SendDebugMessagesToClients is enabled, it emits {"error":"server_error","error_description":"<marshal error>"} with status 500 — the same last-resort path as access_error.go, in the authorize endpoint.

Source

Thrown at fosite/authorize_error.go:30

func (f *Fosite) WriteAuthorizeError(ctx context.Context, rw http.ResponseWriter, ar AuthorizeRequester, err error) {
	rw.Header().Set("Cache-Control", "no-store")
	rw.Header().Set("Pragma", "no-cache")

	if f.ResponseModeHandler(ctx).ResponseModes().Has(ar.GetResponseMode()) {
		f.ResponseModeHandler(ctx).WriteAuthorizeError(ctx, rw, ar, err)
		return
	}

	rfcerr := ErrorToRFC6749Error(err).WithLegacyFormat(f.Config.GetUseLegacyErrorFormat(ctx)).WithExposeDebug(f.Config.GetSendDebugMessagesToClients(ctx)).WithLocalizer(f.Config.GetMessageCatalog(ctx), getLangFromRequester(ar))
	if !ar.IsRedirectURIValid() {
		rw.Header().Set("Content-Type", "application/json;charset=UTF-8")

		js, err := json.Marshal(rfcerr)
		if err != nil {
			if f.Config.GetSendDebugMessagesToClients(ctx) {
				errorMessage := EscapeJSONString(err.Error())
				http.Error(rw, fmt.Sprintf(`{"error":"server_error","error_description":"%s"}`, errorMessage), http.StatusInternalServerError)
			} else {
				http.Error(rw, `{"error":"server_error"}`, http.StatusInternalServerError)
			}
			return
		}

		rw.WriteHeader(rfcerr.CodeField)
		_, _ = rw.Write(js)
		return
	}

	redirectURI := ar.GetRedirectURI()

	// The endpoint URI MUST NOT include a fragment component.
	redirectURI.Fragment = ""

	errors := rfcerr.ToValues()
	errors.Set("state", ar.GetState())

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Fix the root cause of the authorize error first (valid client_id and redirect_uri registration) so the redirect branch is used instead of JSON rendering.
  2. Audit custom authorize handlers and storage errors for non-serializable values attached to fosite errors.
  3. Disable SendDebugMessagesToClients in production so marshal failures return the generic {"error":"server_error"} body.
  4. Add a regression test marshaling your custom error type through WriteAuthorizeError.

Example fix

// before
return errors.Wrapf(err, "db fail: %v", conn) // conn may be unserializable when fosite re-wraps
// after
return fosite.ErrServerError.WithHint("storage unavailable")
// and register redirect URIs so errors redirect instead of JSON-rendering:
// client.RedirectURIs = []string{"https://app.example.com/callback"}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify client registration prevents the JSON branch (always provide a valid redirect):
if len(client.GetRedirectURIs()) == 0 {
	return errors.New("client has no registered redirect URIs — authorize errors will JSON-render")
}

Type guard

func authorizeErrorRedirectable(err *fosite.RFC6749Error) bool {
	return err.ReasonCode != fosite.ErrInvalidRequestURI.ReasonCode && len(err.Hint) > 0
}

Try / catch

// Ensure errors redirect where possible; wrap WriteAuthorizeError to log first:
func writeAuthorizeError(ctx context.Context, f *fosite.Fosite, rw http.ResponseWriter, ar fosite.AccessRequester, err error) {
	log.Printf("authorize error: %v", err)
	f.WriteAuthorizeError(ctx, rw, ar, err)
}

Prevention

When it happens

Trigger: WriteAuthorizeError takes the JSON branch (no valid redirect possible) and json.Marshal(rfcerr) fails at fosite/authorize_error.go:30 — custom error/args values attached by an authorize handler containing channels, funcs, or cyclic references; also triggered when the client's redirect URI is invalid so fosite must render JSON instead of redirecting.

Common situations: Authorization requests with unregistered client_id or disallowed redirect_uri (forces the JSON branch) combined with custom handlers that stash unserializable data on the error; debug mode enabled; custom storage returning errors wrapped with unserializable context.

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/9db8c4051546f2b2. Report an issue: GitHub.