Skip to content

Request Object accepts alg=none when client omits request_object_signing_alg registration #876

Description

@ayadlin

Summary

When fosite handles an OAuth2 / OIDC authorization request whose request parameter is a JWT (Request Object), the keyfunc accepts alg=none JWTs unconditionally when the client did not register request_object_signing_alg. The unsigned Request Object's parameters (scope, state, nonce, claims, prompt, acr_values, etc.) then override the query-string parameters via the post-parse claim-copy loop, with no signature check.

OIDC Core 1.0 §6.1 permits this technically — the default-when-omitted text says "any algorithm supported by the OP and the RP MAY be used" — but most production OPs (Keycloak, Auth0, Okta, Entra) reject none by default for safety. This issue proposes flipping fosite's default to match, since accepting none for clients that did not explicitly opt in is a meaningful default-fail: any integration that omits the alg-registration field (accidentally or not) exposes the authorization endpoint to unauthenticated parameter override.

Verified on

master at HEAD a5f0b09 (2026-05-09). Same code is bundled in ory/hydra/v2/fosite/authorize_request_handler.go:97–99 at HEAD a54baeb.

Affected code

authorize_request_handler.go:87–125:

token, err := jwt.ParseWithClaims(assertion, jwt.MapClaims{}, func(t *jwt.Token) (interface{}, error) {
    if oidcClient.GetRequestObjectSigningAlgorithm() != "" && oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) {
        return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf(...))
    }

    if t.Method == jwt.SigningMethodNone {
        return jwt.UnsafeAllowNoneSignatureType, nil  // <-- accepts unsigned RO unless client opted out
    }

    switch t.Method {
    case jose.RS256, jose.RS384, jose.RS512:
        // ...
    }
})

Control flow:

  1. If the client registered an alg and it doesn't match the JWT header — reject. ✓
  2. If the client registered no alg, or registered none, or registered the matching alg — fall through.
  3. If t.Method == none — return UnsafeAllowNoneSignatureType, which the JWT library treats as "accept the unsigned token."

There is no guard like if oidcClient.GetRequestObjectSigningAlgorithm() == "" && t.Method == none { return ErrInvalidRequestObject }.

Impact when triggered

After the JWT is "verified," the post-parse claim-copy loop overrides any matching query-string parameter:

for k, v := range claims {
    request.Form.Set(k, fmt.Sprintf("%s", v))
}

So the unauthenticated party can choose:

  • redirect_uri (subject to the existing redirect_uri allowlist — so this is constrained, not full ATO)
  • scope
  • state (param the user/agent expects to round-trip — CSRF / state-fixation)
  • nonce (ID-token replay setup if RP doesn't enforce per-request nonce uniqueness)
  • claims (OIDC claims-request structure)
  • acr_values, prompt, max_age, display, ui_locales, id_token_hint, etc.

Practical impact depends on the deployment and what other defenses (PKCE, PAR, cnf-bound DPoP, redirect_uri exact-match, response_mode constraints) are in place. The unifying point: the only deployment characteristic that distinguishes "exploitable" from "irrelevant" is whether the client registered request_object_signing_alg.

What OIDC Core says

OIDC Core 1.0 §6.1:

request_object_signing_alg — OPTIONAL. … Servers SHOULD support RS256. The value none MAY be used. The default, if omitted, is that any algorithm supported by the OP and the RP MAY be used.

"MAY be used" is not "MUST be accepted." A conformant OP can choose to reject none by default and require explicit opt-in via request_object_signing_alg=none. That is the posture most other OPs take and is the safer default.

Suggested remediation

Tighten the default to require explicit opt-in for none:

    if oidcClient.GetRequestObjectSigningAlgorithm() != "" && oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) {
        return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf(...))
    }

    if t.Method == jwt.SigningMethodNone {
+       if oidcClient.GetRequestObjectSigningAlgorithm() != "none" {
+           return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf(
+               "Request object uses 'none' signing algorithm, but the OAuth 2.0 Client " +
+               "did not register request_object_signing_alg=none. Per security policy, " +
+               "'none' must be explicitly opted in via dynamic client registration."))
+       }
        return jwt.UnsafeAllowNoneSignatureType, nil
    }

For deployments needing the previous lenient behavior during a migration, expose a config knob (e.g., oidc.request_object.allow_unregistered_none_alg) defaulting to false. That gives operators a documented escape hatch while inverting the default to the safe choice.

Migration impact: clients that work today with alg=none Request Objects without having registered none will start failing after the default flip. Worth running telemetry on the existing fleet to count affected clients before shipping.

Why GitHub issue rather than HackerOne

The current behavior is permitted by OIDC Core's text. A HackerOne reviewer would be within their rights to close this as "intended behavior per spec." The right channel is a public hardening request: explain the threat, propose a backwards-compatible default flip, leave the policy decision to fosite's maintainers.

Happy to send a PR if the maintainers want to ship the default flip.

References

  • authorize_request_handler.go:87–125 (master HEAD a5f0b09)
  • ory/hydra bundled copy: fosite/authorize_request_handler.go:87–125 (HEAD a54baeb)
  • OpenID Connect Core 1.0 §6.1 — Passing Request Parameters as JWTs
  • CWE-345 (insufficient verification of data authenticity), CWE-1188 (insecure default initialization)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions