Skip to content

Commit 90e6a79

Browse files
committed
Add documentation for OAuth 2.0 Pushed Authorization Requests (PAR)
Closes gh-2014
1 parent 3debeb6 commit 90e6a79

File tree

5 files changed

+166
-22
lines changed

5 files changed

+166
-22
lines changed

docs/modules/ROOT/pages/configuration-model.adoc

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,18 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
118118
.tokenGenerator(tokenGenerator) <5>
119119
.clientAuthentication(clientAuthentication -> { }) <6>
120120
.authorizationEndpoint(authorizationEndpoint -> { }) <7>
121-
.deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> { }) <8>
122-
.deviceVerificationEndpoint(deviceVerificationEndpoint -> { }) <9>
123-
.tokenEndpoint(tokenEndpoint -> { }) <10>
124-
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <11>
125-
.tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <12>
126-
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <13>
121+
.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint -> { }) <8>
122+
.deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> { }) <9>
123+
.deviceVerificationEndpoint(deviceVerificationEndpoint -> { }) <10>
124+
.tokenEndpoint(tokenEndpoint -> { }) <11>
125+
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <12>
126+
.tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <13>
127+
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <14>
127128
.oidc(oidc -> oidc
128-
.providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <14>
129-
.logoutEndpoint(logoutEndpoint -> { }) <15>
130-
.userInfoEndpoint(userInfoEndpoint -> { }) <16>
131-
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <17>
129+
.providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <15>
130+
.logoutEndpoint(logoutEndpoint -> { }) <16>
131+
.userInfoEndpoint(userInfoEndpoint -> { }) <17>
132+
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <18>
132133
)
133134
);
134135
@@ -142,16 +143,17 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
142143
<5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server.
143144
<6> `clientAuthentication()`: The configurer for xref:configuration-model.adoc#configuring-client-authentication[OAuth2 Client Authentication].
144145
<7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint].
145-
<8> `deviceAuthorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization endpoint].
146-
<9> `deviceVerificationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification endpoint].
147-
<10> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
148-
<11> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint].
149-
<12> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
150-
<13> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint].
151-
<14> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint].
152-
<15> `logoutEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-logout-endpoint[OpenID Connect 1.0 Logout endpoint].
153-
<16> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
154-
<17> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
146+
<8> `pushedAuthorizationRequestEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-pushed-authorization-request-endpoint[OAuth2 Pushed Authorization Request endpoint].
147+
<9> `deviceAuthorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization endpoint].
148+
<10> `deviceVerificationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification endpoint].
149+
<11> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
150+
<12> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint].
151+
<13> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
152+
<14> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint].
153+
<15> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint].
154+
<16> `logoutEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-logout-endpoint[OpenID Connect 1.0 Logout endpoint].
155+
<17> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
156+
<18> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
155157

156158
[[configuring-authorization-server-settings]]
157159
== Configuring Authorization Server Settings
@@ -169,6 +171,7 @@ public final class AuthorizationServerSettings extends AbstractSettings {
169171
public static Builder builder() {
170172
return new Builder()
171173
.authorizationEndpoint("/oauth2/authorize")
174+
.pushedAuthorizationRequestEndpoint("/oauth2/par")
172175
.deviceAuthorizationEndpoint("/oauth2/device_authorization")
173176
.deviceVerificationEndpoint("/oauth2/device_verification")
174177
.tokenEndpoint("/oauth2/token")
@@ -200,6 +203,7 @@ public AuthorizationServerSettings authorizationServerSettings() {
200203
return AuthorizationServerSettings.builder()
201204
.issuer("https://example.com")
202205
.authorizationEndpoint("/oauth2/v1/authorize")
206+
.pushedAuthorizationRequestEndpoint("/oauth2/v1/par")
203207
.deviceAuthorizationEndpoint("/oauth2/v1/device_authorization")
204208
.deviceVerificationEndpoint("/oauth2/v1/device_verification")
205209
.tokenEndpoint("/oauth2/v1/token")

docs/modules/ROOT/pages/overview.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Spring Authorization Server supports the following features:
8282
|xref:protocol-endpoints.adoc[Protocol Endpoints]
8383
|
8484
* xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization Endpoint]
85+
* xref:protocol-endpoints.adoc#oauth2-pushed-authorization-request-endpoint[OAuth2 Pushed Authorization Request Endpoint]
8586
* xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization Endpoint]
8687
* xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification Endpoint]
8788
* xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token Endpoint]
@@ -97,6 +98,8 @@ Spring Authorization Server supports the following features:
9798
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[draft])
9899
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.1[Authorization Endpoint]
99100
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.2[Token Endpoint]
101+
* OAuth 2.0 Pushed Authorization Requests (https://datatracker.ietf.org/doc/html/rfc9126[RFC 9126])
102+
** https://datatracker.ietf.org/doc/html/rfc9126#section-2[Pushed Authorization Request Endpoint]
100103
* OAuth 2.0 Device Authorization Grant (https://tools.ietf.org/html/rfc8628[RFC 8628])
101104
** https://tools.ietf.org/html/rfc8628#section-3.1[Device Authorization Endpoint]
102105
** https://tools.ietf.org/html/rfc8628#section-3.3[Device Verification Endpoint]

docs/modules/ROOT/pages/protocol-endpoints.adoc

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,129 @@ static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationC
126126
}
127127
----
128128

129+
[[oauth2-pushed-authorization-request-endpoint]]
130+
== OAuth2 Pushed Authorization Request Endpoint
131+
132+
`OAuth2PushedAuthorizationRequestEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc9126#section-2[OAuth2 Pushed Authorization Request endpoint].
133+
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc9126#section-2.1[OAuth2 Pushed Authorization requests].
134+
135+
`OAuth2PushedAuthorizationRequestEndpointConfigurer` provides the following configuration options:
136+
137+
[source,java]
138+
----
139+
@Bean
140+
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
141+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
142+
OAuth2AuthorizationServerConfigurer.authorizationServer();
143+
144+
http
145+
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
146+
.with(authorizationServerConfigurer, (authorizationServer) ->
147+
authorizationServer
148+
.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint ->
149+
pushedAuthorizationRequestEndpoint
150+
.pushedAuthorizationRequestConverter(pushedAuthorizationRequestConverter) <1>
151+
.pushedAuthorizationRequestConverters(pushedAuthorizationRequestConvertersConsumer) <2>
152+
.authenticationProvider(authenticationProvider) <3>
153+
.authenticationProviders(authenticationProvidersConsumer) <4>
154+
.pushedAuthorizationResponseHandler(pushedAuthorizationResponseHandler) <5>
155+
.errorResponseHandler(errorResponseHandler) <6>
156+
)
157+
);
158+
159+
return http.build();
160+
}
161+
----
162+
<1> `pushedAuthorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc9126#section-2.1[OAuth2 pushed authorization request] from `HttpServletRequest` to an instance of `OAuth2PushedAuthorizationRequestAuthenticationToken`.
163+
<2> `pushedAuthorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
164+
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2PushedAuthorizationRequestAuthenticationToken`.
165+
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
166+
<5> `pushedAuthorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2PushedAuthorizationRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc9126#section-2.2[OAuth2 pushed authorization response].
167+
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc9126#section-2.3[OAuth2Error response].
168+
169+
`OAuth2PushedAuthorizationRequestEndpointConfigurer` configures the `OAuth2PushedAuthorizationRequestEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
170+
`OAuth2PushedAuthorizationRequestEndpointFilter` is the `Filter` that processes OAuth2 pushed authorization requests.
171+
172+
`OAuth2PushedAuthorizationRequestEndpointFilter` is configured with the following defaults:
173+
174+
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter`.
175+
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2PushedAuthorizationRequestAuthenticationProvider`.
176+
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2PushedAuthorizationRequestAuthenticationToken` and returns the OAuth2 pushed authorization response.
177+
* `*AuthenticationFailureHandler*` -- An `OAuth2ErrorAuthenticationFailureHandler`.
178+
179+
[[oauth2-pushed-authorization-request-endpoint-customizing-authorization-request-validation]]
180+
=== Customizing Pushed Authorization Request Validation
181+
182+
`OAuth2AuthorizationCodeRequestAuthenticationValidator` is the default validator used for validating specific OAuth2 pushed authorization request parameters used in the Authorization Code Grant.
183+
The default implementation validates the `redirect_uri` and `scope` parameters.
184+
If validation fails, an `OAuth2AuthorizationCodeRequestAuthenticationException` is thrown.
185+
186+
`OAuth2PushedAuthorizationRequestAuthenticationProvider` provides the ability to override the default pushed authorization request validation by supplying a custom authentication validator of type `Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>` to `setAuthenticationValidator()`.
187+
188+
[TIP]
189+
`OAuth2AuthorizationCodeRequestAuthenticationContext` holds the `OAuth2AuthorizationCodeRequestAuthenticationToken`, which contains the OAuth2 pushed authorization request parameters.
190+
191+
[IMPORTANT]
192+
If validation fails, the authentication validator *MUST* throw `OAuth2AuthorizationCodeRequestAuthenticationException`.
193+
194+
A common use case during the development life cycle phase is to allow for `localhost` in the `redirect_uri` parameter.
195+
196+
The following example shows how to configure `OAuth2PushedAuthorizationRequestAuthenticationProvider` with a custom authentication validator that allows for `localhost` in the `redirect_uri` parameter:
197+
198+
[source,java]
199+
----
200+
@Bean
201+
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
202+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
203+
OAuth2AuthorizationServerConfigurer.authorizationServer();
204+
205+
http
206+
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
207+
.with(authorizationServerConfigurer, (authorizationServer) ->
208+
authorizationServer
209+
.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint ->
210+
pushedAuthorizationRequestEndpoint
211+
.authenticationProviders(configureAuthenticationValidator())
212+
)
213+
);
214+
215+
return http.build();
216+
}
217+
218+
private Consumer<List<AuthenticationProvider>> configureAuthenticationValidator() {
219+
return (authenticationProviders) ->
220+
authenticationProviders.forEach((authenticationProvider) -> {
221+
if (authenticationProvider instanceof OAuth2PushedAuthorizationRequestAuthenticationProvider) {
222+
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
223+
// Override default redirect_uri validator
224+
new CustomRedirectUriValidator()
225+
// Reuse default scope validator
226+
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR);
227+
228+
((OAuth2PushedAuthorizationRequestAuthenticationProvider) authenticationProvider)
229+
.setAuthenticationValidator(authenticationValidator);
230+
}
231+
});
232+
}
233+
234+
static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> {
235+
236+
@Override
237+
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
238+
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
239+
authenticationContext.getAuthentication();
240+
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
241+
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
242+
243+
// Use exact string matching when comparing client redirect URIs against pre-registered URIs
244+
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
245+
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
246+
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
247+
}
248+
}
249+
}
250+
----
251+
129252
[[oauth2-device-authorization-endpoint]]
130253
== OAuth2 Device Authorization Endpoint
131254

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationU
6363
* @param authorizationUri the authorization URI
6464
* @param clientId the client identifier
6565
* @param principal the authenticated client principal
66-
* @param requestUri the request URI corresponding to the authorization request posted
66+
* @param requestUri the {@code request_uri} corresponding to the authorization
67+
* request posted
6768
* @param requestUriExpiresAt the expiration time on or after which the
68-
* {@code requestUri} MUST NOT be accepted
69+
* {@code request_uri} MUST NOT be accepted
6970
* @param redirectUri the redirect uri
7071
* @param state the state
7172
* @param scopes the authorized scope(s)
@@ -81,11 +82,21 @@ public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationU
8182
setAuthenticated(true);
8283
}
8384

85+
/**
86+
* Returns the {@code request_uri} corresponding to the authorization request posted.
87+
* @return the {@code request_uri} corresponding to the authorization request posted
88+
*/
8489
@Nullable
8590
public String getRequestUri() {
8691
return this.requestUri;
8792
}
8893

94+
/**
95+
* Returns the expiration time on or after which the {@code request_uri} MUST NOT be
96+
* accepted.
97+
* @return the expiration time on or after which the {@code request_uri} MUST NOT be
98+
* accepted
99+
*/
89100
@Nullable
90101
public Instant getRequestUriExpiresAt() {
91102
return this.requestUriExpiresAt;

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.springframework.security.crypto.keygen.StringKeyGenerator;
2323

2424
/**
25+
* A representation of a {@code request_uri} used in OAuth 2.0 Pushed Authorization
26+
* Requests.
27+
*
2528
* @author Joe Grandja
2629
* @since 1.5
2730
*/

0 commit comments

Comments
 (0)