[← 07 Diagnostics](07-diagnostics.md) · [index](README.md) · next: [09 — The entry point and the Accept header](09-entry-point.md) # The relying party Source: [`ClientSecurityConfig.java`](../../authorization-server/oidc-client/src/main/java/com/ankurm/client/ClientSecurityConfig.java), [`HomeController.java`](../../authorization-server/oidc-client/src/main/java/com/ankurm/client/HomeController.java), [`PkceConfig.java`](../../authorization-server/oidc-client/src/main/java/com/ankurm/client/PkceConfig.java). ## The whole client side, in one method ```java http .authorizeHttpRequests(auth -> auth.requestMatchers("/", "/error").permitAll() .anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oauth2Client(Customizer.withDefaults()) .logout(logout -> logout.logoutSuccessUrl("/")); ``` Plus one provider entry and one registration in `application.yaml`. Spring reads `/.well-known/openid-configuration` at first use and fills in every endpoint from it. ## Run the client on 127.0.0.1, not localhost The authorization server is on `localhost:9000` and the client on `127.0.0.1:8080`. Those are different origins to a browser cookie jar. Put both on `localhost` and the two `JSESSIONID` cookies collide — one app's session clobbers the other's — and you get a login loop that looks like a Spring Security bug. ## Use the access token, not the id_token ```java @GetMapping("/orders") public String orders(@RegisteredOAuth2AuthorizedClient("demo-web") OAuth2AuthorizedClient client, …) ``` `@RegisteredOAuth2AuthorizedClient` hands you the access token Spring already holds. Reading a token out of the `OidcUser` gives you the *id_token* instead, which produces a 401 from a resource server with a token that looks perfectly valid — because it is; it is just the wrong one. See [05](05-token-customisation.md). ## The full flow, hop by hop [`as-client-flow.txt`](../output/as-client-flow.txt) is the real thing, driven with curl so every redirect is visible: ``` 302 http://127.0.0.1:8080/orders 302 http://127.0.0.1:8080/oauth2/authorization/demo-web 302 http://localhost:9000/oauth2/authorize?…&code_challenge=…&code_challenge_method=S256 200 http://localhost:9000/login 302 POST http://localhost:9000/login 302 http://localhost:9000/oauth2/authorize?…&continue 200 http://localhost:9000/oauth2/consent?… 302 POST http://localhost:9000/oauth2/authorize 302 http://127.0.0.1:8080/login/oauth2/code/demo-web?code=g51U-dZi…&state=… 200 http://127.0.0.1:8080/orders ``` Nine hops for one login. Ending with the resource server's answer rendered by the client: ``` {orders=[{id=1, total=42.00}], subject=alice, scopes=[orders.write, openid, profile, orders.read], roles=[ADMIN, USER], tenant=acme, audience=[orders-api]} ``` ## The client-side PKCE rule `DefaultOAuth2AuthorizationRequestResolver.getBuilder(...)`, disassembled ([`as-pkce-applier.txt`](../output/as-pkce-applier.txt)), applies its PKCE customizer when **either** the registration's authentication method is `NONE` **or** `registration.getClientSettings().isRequireProofKey()`: ``` 57: getstatic ClientAuthenticationMethod.NONE 64: invokevirtual ClientAuthenticationMethod.equals 67: ifne 80 71: invokevirtual ClientRegistration$ClientSettings.isRequireProofKey 77: ifeq 89 80: getstatic DEFAULT_PKCE_APPLIER ``` And `ClientRegistration.ClientSettings.Builder` initialises `requireProofKey` to `false` in Spring Security 6.5.1 and to `true` in 7.1.1. So a confidential Spring client now sends PKCE where it previously did not. Note the consequence for configuration: setting an *authorization request customizer* can turn PKCE on, but cannot turn it off, because the default applier runs inside `getBuilder` independently of the customizer. To disable it you have to rebuild the `ClientRegistration` with `requireProofKey(false)`, which is what the `nopkce` profile does. ## What a pre-7.0 client looks like against a 7.1 server [`as-client-flow-nopkce.txt`](../output/as-client-flow-nopkce.txt): ``` 302 http://127.0.0.1:8080/oauth2/authorization/demo-web 302 http://localhost:9000/oauth2/authorize?response_type=code&client_id=demo-web&…&nonce=… >>> NO code_challenge 302 http://127.0.0.1:8080/login/oauth2/code/demo-web ?error=invalid_request &error_description=OAuth%202.0%20Parameter%3A%20code_challenge 200 http://127.0.0.1:8080/login?error ``` The user never sees a login page. They land on the **client's** error page, and nothing in the client's logs names the provider as the cause — the reason exists only in a query string that the client discards. Fix it on either side: `requireProofKey(false)` on the `RegisteredClient`, or `OAuth2AuthorizationRequestCustomizers.withPkce()` on the client. Prefer the second. ## A dependency-cycle trap A `@Bean` that takes `ClientRegistrationRepository` and returns one is a cycle, and Boot refuses to start: ``` Relying upon circular references is discouraged and they are prohibited by default. ``` Post-process the repository Boot already built with a `static BeanPostProcessor` instead. Next: [09 — The entry point and the Accept header](09-entry-point.md)