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}