Validation and failure cases
A pushed request can fail in two places. The push itself can be refused, in a direct response to the printer's backend. Or the push succeeds, and the browser's later visit with the reference is refused. The two failures look different, reach different parties, and call for different responses.
Errors from the push
All domains and identifiers in these examples are fictional. Suppose a developer changed the printer's callback path in the code but not in its registration. The push fails:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-cache, no-store
{
"error": "invalid_request",
"error_description": "The redirect_uri is not valid for this client"
}
The error uses the same JSON format as a token endpoint error. Its code comes from the token endpoint's list or the authorization endpoint's list, whichever fits. A return address problem is a useful case to notice. In the browser, that mistake would have left you on an error page at the photo service, because the service may not redirect to an address it cannot trust and the specification defines no error code for that case. At the PAR endpoint there is no redirect to worry about, so the service can say what went wrong, using invalid_request by default.
| Response | Usual cause | The printer's next step |
|---|---|---|
400 invalid_request | A malformed request, or a return address not valid for this client. | Treat it as a configuration fault. Retrying the same push will fail the same way. |
401 invalid_client | Client authentication failed. | Check the deployed credential against the registration, as for the token endpoint. |
400 invalid_scope or unauthorized_client | The printer asked for something its registration does not allow. | Fix the request or the registration. |
413 | The request is larger than the service accepts. | Reduce the request. |
429 | Too many pushes from this client in a period. | Wait before trying again. |
Some errors never come from this endpoint. Because nobody has been asked to sign in or approve anything yet, errors about signing in or consent are decided later, in the browser. A request sent with any method other than POST gets 405.
Whatever the error, your browser has not gone anywhere. You are still on the printer's page, so the printer can tell you that it could not connect your photo account right now and record a safe diagnostic for its developers. It should not fall back to building an ordinary authorization URL, which would quietly give up the protection it chose PAR for.
Parameters in two places
The printer sends only client_id and request_uri to the authorization endpoint. Suppose someone edits the address in the browser to add more:
https://auth.photos.example/authorize
?client_id=photo-printer
&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Ademo-pushed-request-7
&scope=photos.read%20photos.delete
The added scope has no effect. Under the borrowed rules, the service uses only the parameters it stored for the reference. Values added to the URL are ignored, and a stricter service refuses a request that carries them. Either way, an edit in the browser cannot change what the printer pushed. High-assurance profiles go further and require clients to send nothing but client_id and request_uri to the authorization endpoint.
The same rule explains why client_id must still match. It is the one value that appears in both places, and a request where the browser names one client while the reference belongs to another is refused.
Requiring PAR and choosing return addresses
A service that offers PAR but still accepts ordinary authorization URLs has added a safer route without closing the old one. Anyone can still write an ordinary URL naming the printer, with whatever parameters the registration allows. The protections apply only to requests that take the new route.
Closing the old route is a policy setting, and it can be announced in metadata. For the whole service, it appears in the authorization server's metadata. For one client, it can be recorded in that client's registration:
Authorization server metadata (excerpt):
{
"issuer": "https://auth.photos.example",
"authorization_endpoint": "https://auth.photos.example/authorize",
"pushed_authorization_request_endpoint": "https://auth.photos.example/par",
"require_pushed_authorization_requests": false
}
Client registration for photo-printer (excerpt):
{
"require_pushed_authorization_requests": true
}
Here the photo service does not require PAR from every client, but it does from the printer. Any authorization request for photo-printer that lacks a request URI obtained from the PAR endpoint is refused with invalid_request. Both settings default to false when omitted.
PAR can also change how return addresses are handled. Exact matching against the registered list remains the normal rule. Because the service authenticates the client before the authorization begins, the specification allows a service to relax that rule for clients with credentials, accepting a return address the client supplies in the push without registering it first. A client could then use a distinct address for each authorization server it works with, chosen at run time. The service decides whether to allow this and can still limit it, for example by requiring every such address to start with https://printer.example/oauth/.
That freedom must never extend to unauthenticated pushes. If anyone could push a request with a return address of their choosing, the photo service would send people and their codes wherever an attacker liked. A public client, which cannot authenticate, keeps to its registered addresses.
PAR protects the request by changing the route it takes. JWT-Secured Authorization Requests protects it a different way, by having the client sign it, and the two can be combined.