Table of Contents

Requiring and validating pushed requests

A pushed authorization request (RFC 9126) sends the authorize request's parameters to the server over the back channel, authenticated, and hands the browser a short-lived reference in their place. The exchange is recorded in pushed authorization requests; this page is about the server's rules for it. The code is included from samples/Interaction.Events, whose test pushes requests the server accepts and refuses, and sends one through the front channel that it refuses.

// Every client must push its authorization requests (RFC 9126); discovery says so.
options.PushedAuthorization.Required = true;

// A confidential client's pushes may name a redirect URI it never registered (RFC 9126 §2.4).
options.PushedAuthorization.AllowUnregisteredPushedRedirectUris = true;

Requiring pushing of every client

Required is the server-wide form of Client.RequirePushedAuthorization — what an installation with no legacy clients left should set. It is read in two places: the authorize endpoint refuses any request that arrives without a reference, and the discovery document advertises require_pushed_authorization_requests so a client learns the rule before it sends anything. RFC 9126 §5 makes that a property of the server, so it is advertised only for the server-wide flag; a per-client requirement is not published, because doing so would misinform every other client. Each refusal raises PushedAuthorizationFailureEvent (events).

What the refusal says, since 0.96.0, is Duende's. A request without a reference — or with a request_uri that is not this server's PAR URN, such as a request object URL — is refused with invalid_request, "Pushed authorization is required.", as Duende 8.0.1's RequestObjectValidator refuses it. It was invalid_request_uri until then, which describes a reference the request did not carry; a reference that is malformed, unknown, expired, redeemed or another client's is still invalid_request_uri. And the rule is asked only of a client that resolves to an enabled one, as Duende loads the client first: a request naming no client is invalid_request "Missing client_id.", and an unknown or disabled client unauthorized_client, whatever the server requires of the clients it can serve.

Required = true with Endpoints.EnablePushedAuthorizationEndpoint = false refuses to start, as of 0.54.0. The two settings are individually sensible and together describe a server that can authorize nobody: every request is refused for carrying no reference, and there is no endpoint at which a reference can be obtained. It is a boot failure rather than a runtime surprise because nothing can report the state — require_pushed_authorization_requests is published inside the branch that publishes the endpoint, so with the endpoint off a client is told neither that pushing is required nor where to push, and every refusal that follows looks like its own bug.

A push is validated when it is pushed

New in 0.122.0, as Duende 8.0.1's pushed authorization validator does it: a push is validated as the authorize request it will become — the redirect URI, the response type, the scopes, PKCE, resource indicators, a request object — before it is stored, and your authorize request hook is asked about it then too. A request the authorize endpoint would refuse is refused to the client that pushed it, 400 with the authorize endpoint's error, where until 0.122.0 it was stored and refused to a user's browser when the reference was redeemed.

AllowUnregisteredPushedRedirectUris lets a client with RequireClientSecret name any redirect URI in a pushed request: it authenticated to push it, so the URI is its own word rather than a browser's. The same request redeemed is held to the same rule, a public client never is, and a front-channel request never is — including, since 0.177.0, a request object fetched by request_uri, which this server stores behind a reference of the same shape but which nobody authenticated to send (request objects by reference). A confidential client then needs no registered redirect URI at all, and the configuration validator accepts it. Both options, and the reference's lifetime, are in the configuration reference.

The seams are Duende's: IPushedAuthorizationRequestValidator and IPushedAuthorizationResponseGenerator, each with an open default. IAuthorizeRequestValidator's ValidateAsync takes Duende's AuthorizeRequestType — Authorize, PushedAuthorization or AuthorizeWithPushedParameters — which the validated request and the redirect URI validator's context carry. Breaking in 0.122.0: it comes before the cancellation token, so a call passing the token positionally as the third argument names it instead.