# Describe the pending authorization request (OIDC Core §3.1.2.4)

`POST /v1/auth/consent`

Returns what the client is asking for, resolved server-side from the
validated authorization request. The SPA renders this and nothing from its
own URL: a consent screen showing an attacker-supplied application name or
scope list is a phishing page.

It is a POST rather than a GET because it carries the authorization request
parameters in a body, keeping them out of the URL, browser history and
Referer headers. It changes no state.

## Authorization

None: this endpoint does not take API credentials.

This endpoint reads the browser session from the `Cookie` header: The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.

This endpoint is rate limited; it answers `429` when a limit is hit.

## Headers

- `Origin` (string, optional): Browser Origin, checked (with Sec-Fetch-Site) as a same-origin/CSRF guard.
- `Sec-Fetch-Site` (string, optional): Fetch metadata; only same-origin is trusted (CSRF guard).
- `Content-Type` (string, optional): Must be application/json — a JSON body cannot be a CORS simple request, closing form-based CSRF.
- `Cookie` (string, optional): The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.

## Request body (`application/json`)

The authorization request the screen is about to describe, carried so the server can re-validate it against the client registry and answer for THAT request.
It is deliberately NOT the sign-in body: by this point the user is already authenticated by cookie, and a schema that requires credentials would make an already-signed-in user's request fail validation. The parameters are exactly the ones preserved across the login round trip.

- `client_id` (string, optional): Constraints: at most 256 characters.
- `redirect_uri` (string, optional): Constraints: at most 2048 characters.
- `response_type` (string, optional): Constraints: at most 32 characters.
- `scope` (string, optional): Constraints: at most 2048 characters.
- `state` (string, optional): Constraints: at most 1024 characters.
- `nonce` (string, optional): Constraints: at most 1024 characters.
- `code_challenge` (string, optional): Constraints: at most 128 characters.
- `code_challenge_method` (string, optional): Constraints: at most 16 characters.
- `ui_locales` (string, optional): Constraints: at most 256 characters.
- `request_uri` (string, optional): Constraints: at most 512 characters.
- `resource` (string, optional): Constraints: at most 2048 characters.
- `authorization_details` (string, optional): Constraints: at most 8192 characters.
- `dpop_jkt` (string, optional): Constraints: exactly 43 characters; pattern `^[A-Za-z0-9_-]+$`.
- `max_age` (integer (int64), optional): Constraints: at least 0.
- `prompt` (string, optional): OIDC Core §3.1.2.1 `prompt`. Carried so the screen describes the request the way the resumed /authorize will evaluate it: with prompt=consent the full scope set is re-confirmed, without it only the delta an existing grant does not already cover. Constraints: at most 64 characters.

## Responses

### 200 OK

The pending request, for display before the decision.

Content type: `application/json`

Headers:
- `Cache-Control` = `no-store`

What a client is asking the signed-in user to agree to, resolved SERVER-SIDE from the validated authorization request. Nothing here is taken from the query string: a consent screen that renders an attacker-supplied application name or scope list is a ready-made phishing page (OIDC Core §16.18 on the risks of the authorization UI).

- `client_id` (string, required): Example: `"tv-app"`.
- `scopes` (array of strings, required): The scopes not yet covered by an existing grant. Only the delta is listed: re-asking for what the user already agreed to trains people to click through, which is the opposite of what the screen is for. Example: `["openid","profile"]`.
- `client_name` (string, optional): The registered display name, or the client_id when none is set. Example: `"Living Room TV"`.
- `client_logo_uri` (string (uri), optional)
- `account_email` (string, optional): The signed-in account, so the user can tell WHICH account they are granting from. Example: `"alice@example.com"`.
- `resource` (string (uri), optional): The RFC 8707 resource this agreement would cover, when the request named one.
- `authorization_details_changed` (boolean, optional): The request carries RFC 9396 authorization details the existing grant does not cover. Flagged separately because a structured authorization is not a scope and cannot be listed as one.

### 429 Too Many Requests

Too many requests.

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### Error

Problem details (RFC 9457).

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

## Example request

```bash
curl -X POST 'https://auth.example.com/v1/auth/consent' \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Example response (200)

```json
{
  "client_id": "tv-app",
  "client_name": "Living Room TV",
  "account_email": "alice@example.com",
  "scopes": [
    "openid",
    "profile"
  ]
}
```
