Authorization
POST /v1/oauth/preauthorize and POST /v1/oauth/authorize — the two-step internal endpoints behind the consent page.
These two endpoints are internal interfaces used by the consent UI, not something third-party clients call. Third parties use the standard authorization code flow, described in Authorization code flow.
They turn "the user has seen and approved this application's permissions" into a fact the server can verify: preauthorize validates the identity assertion and the redirect address and issues a consent ticket, then authorize consumes the assertion and ticket and either issues a code or denies.
POST /preauthorize
Request
| Parameter | Type | Required | Notes |
|---|---|---|---|
id_assertion | string | ✅ | JWE issued by identity assertion |
client_id | string | ✅ | Client requesting authorization |
redirect_uri | string | Optional | Redirect address used by the client |
scope | string | Optional | Scope being requested; the client's registered set when omitted |
Response
{
"success": true,
"can_skip_consent": false,
"client_name": "Example App",
"client_type": "third_party",
"client_logo_uri": null,
"client_uri": null,
"client_policy_uri": null,
"scopes": ["openid", "profile"],
"valid_redirect_uri": "https://app.example.com/callback",
"consent_ticket": "ct_..."
}| Field | Notes |
|---|---|
can_skip_consent | Whether the consent screen may be skipped; it does not mean the ticket may be skipped |
client_type | first_party or third_party |
client_*_uri | Presentation material, nullable; the page shows or hides each item individually |
scopes | Scopes to display, computed by the server and written into the ticket |
valid_redirect_uri | The validated redirect address |
consent_ticket | Opaque ticket, valid 30 minutes, redeemable once |
Validation order
- Decrypt the identity assertion, check audience, expiry and session binding;
- consume the assertion (one assertion yields at most one ticket);
- confirm the client exists and the redirect address is registered;
- issue the ticket.
POST /authorize
Request
| Parameter | Type | Required | Notes |
|---|---|---|---|
id_assertion | string | ✅ | Assertion signed at the moment of the click |
consent_ticket | string | ✅ | Ticket returned by preauthorize |
decision | allow | deny | Optional | Defaults to allow |
client_id | string | Optional | Client identifier |
redirect_uri | string | Optional | Redirect address |
response_type | string | Optional | Normally code |
scope | string | Optional | Requested scope |
state | string | Optional | Echoed back verbatim |
code_challenge / code_challenge_method | string | Optional | PKCE |
acr_values | string | Optional | Target authentication strength |
max_age | number | string | Optional | Maximum allowed authentication age |
authorization_details | unknown | Optional | RFC 9396 fine-grained authorization |
Undeclared extension parameters are preserved and passed to the authorization server library rather than silently dropped.
Response
{ "success": true, "redirect_to": "https://app.example.com/callback?code=...&state=..." }With decision=deny, redirect_to points at the callback with error=access_denied.
JSON instead of a 302
The caller is the auth frontend, and it must stay on its own page while deciding when to navigate: a 302 handed straight to the browser would leave the frontend without the "authorization succeeded" result and unable to clean up before navigating.
The practical problem is cross-origin: a frontend calling this endpoint with fetch cannot read Location, so a 302 is simply unusable.
Why the ticket is mandatory
| Without it | Consequence |
|---|---|
| No ticket | /authorize degrades into "an assertion buys a code", so anyone with a valid session can mint codes and the consent page becomes decoration |
| Ticket without data | The account, client, displayed scopes and validated redirect address all live in the server-side record; the caller only returns an opaque string, so the comparison basis never depends on caller input |
Related
- How assertions are signed: Identity assertion
- Full parameters for authorization requests: Authorization code flow
- Fine-grained authorization: Authorization details