Table of Contents

Pushed authorization requests (PAR)

The client posts its authorization request straight to the server and sends the browser only a reference to it, so the parameters never pass through the browser where they could be read or altered. The reference (request_uri) is single-use and short-lived. The client is the demo host's par-client, registered with RequirePushedAuthorization: it cannot start an authorization request any other way.

Follows: RFC 9126, RFC 7636 (PKCE).

Recorded from the demo host (src/Fdyay.IdentityServer.Host) by the PushedAuthorizationFlow test on every CI run. A value in braces, such as {access_token_1}, is one that differs on every run; the same placeholder is the same value wherever it appears. See how these pages are made.

sequenceDiagram
    participant Browser
    participant AuthorizationServer as Authorization Server
    participant Client
    Note over Browser,AuthorizationServer: The user is already signed in at the authorization<br/>server, as in the authorization code flow.
    Client->>AuthorizationServer: POST /connect/par
    AuthorizationServer-->>Client: 201 Created
    Browser->>AuthorizationServer: GET /connect/authorize
    AuthorizationServer-->>Browser: 302 Found to https://client.example.com/callback
    Client->>AuthorizationServer: POST /connect/token
    AuthorizationServer-->>Client: 200 OK
    Browser->>AuthorizationServer: GET /connect/authorize
    AuthorizationServer-->>Browser: 302 Found to /account/error

The user is already signed in at the authorization server, as in the authorization code flow.

1. The client pushes the request

Every parameter the client would have put on the authorize URL, posted to the PAR endpoint instead. The answer is 201 Created with the reference and how long it lasts.

Client → Authorization Server

POST /connect/par HTTP/1.1
Content-Type: application/x-www-form-urlencoded

client_id=par-client
&response_type=code
&scope=openid+profile+api
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
&code_challenge=_VOp_w53jMkBWlSwOlY2MntCuqr-wFZf-TdML17xmn4
&code_challenge_method=S256

Authorization Server → Client

HTTP/1.1 201 Created
Content-Type: application/json; charset=UTF-8
Cache-Control: no-store, no-cache, max-age=0
Pragma: no-cache

{
  "request_uri": "{request_uri_1}",
  "expires_in": 600
}

2. The browser goes to the authorize endpoint with only the reference

The server resumes the pushed request, finds the user signed in, and issues a code.

Browser → Authorization Server

GET /connect/authorize?client_id=par-client
    &request_uri={request_uri_1} HTTP/1.1
Cookie: .AspNetCore.Antiforgery.{suffix}={antiforgery_cookie_1}; idsrv={authentication_cookie_1}

Authorization Server → Browser

HTTP/1.1 302 Found
Location: https://client.example.com/callback?code={authorization_code_1}
    &state=af0ifjsldkj
    &session_state={session_state_1}
    &iss=http%3A%2F%2Flocalhost
Cache-Control: no-cache,no-store
Pragma: no-cache
Set-Cookie: idsrv.session={session_id_1}; path=/; secure; samesite=none
Set-Cookie: idsrv={authentication_cookie_2}; path=/; samesite=none; httponly

3. The client redeems the code

As in any authorization code flow, with the PKCE verifier.

Client → Authorization Server

POST /connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={authorization_code_1}
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback
&client_id=par-client
&code_verifier=documented-flow-code-verifier-0123456789-abcdefghij

Authorization Server → Client

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Cache-Control: no-store, no-cache, max-age=0
Pragma: no-cache

{
  "access_token": "{access_token_1}",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile api",
  "id_token": "{id_token_1}"
}

{access_token_1} decoded — the header, then the payload; the signature is not shown:

{
  "alg": "RS256",
  "kid": "{key_id_1}",
  "typ": "at+jwt"
}
{
  "aud": [
    "api-resource"
  ],
  "iss": "http://localhost",
  "iat": {iat},
  "nbf": {iat},
  "exp": {iat + 3600},
  "jti": "{jti_1}",
  "client_id": "par-client",
  "scope": "api openid profile",
  "sub": "alice",
  "auth_time": {auth_time},
  "idp": "local",
  "amr": [
    "pwd"
  ],
  "sid": "{session_id_1}"
}

{id_token_1} decoded — the header, then the payload; the signature is not shown:

{
  "alg": "RS256",
  "kid": "{key_id_1}",
  "typ": "JWT"
}
{
  "aud": [
    "par-client"
  ],
  "iss": "http://localhost",
  "iat": {iat},
  "nbf": {iat},
  "exp": {iat + 300},
  "jti": "{jti_2}",
  "azp": "par-client",
  "sub": "alice",
  "auth_time": {auth_time},
  "idp": "local",
  "amr": [
    "pwd"
  ],
  "name": "Alice Smith",
  "nonce": "n-0S6_WzA2Mj",
  "sid": "{session_id_1}",
  "at_hash": "{at_hash_1}"
}

4. The reference cannot be used twice

Browser → Authorization Server

GET /connect/authorize?client_id=par-client
    &request_uri={request_uri_1} HTTP/1.1
Cookie: .AspNetCore.Antiforgery.{suffix}={antiforgery_cookie_1}; idsrv={authentication_cookie_2}

Authorization Server → Browser

HTTP/1.1 302 Found
Location: /account/error?errorId={error_id_1}