A browser client cannot keep a secret; PKCE protects its authorization-code exchange, while client credentials represent a service rather than a signed-in user.
Spring Authorization Server grants: PKCE for public clients, credentials for services
Start with client capability
The parcel dashboard runs in a browser, so a bundled client secret would be visible to its users. Register it as a public client with an exact redirect location, authorization-code grant and required PKCE proof. A backend relay with a protected secret can be a confidential client and request client-credentials tokens for service-to-service work. Those tokens do not carry an operator session. Browser login and service-token caching solve different problems.
Check the grant against the API
A resource server should authorize a client-credentials token as a service identity, not manufacture a user from its subject. Keep scopes narrow, validate audience, and decide whether a human action needs a delegated user token instead. An exact redirect allowlist is part of the authorization-code boundary; a loose wildcard can hand a code to an unintended page. PKCE binds the code exchange to the client that started it, but does not replace redirect validation or API authorization.
Try the attack-shaped cases
Reject a code exchange with the wrong verifier, a redirect location absent from registration, and a browser request using a service token for an operator-only endpoint. The registration sketch below illustrates a public client; supply the redirect location from reviewed configuration, and run protocol tests against the selected Spring Security version before publishing the client.
Implementation sketch
RegisteredClient browserClient(String reviewedRedirect) {
return RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("parcel-dashboard")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(reviewedRedirect)
.scope("parcel.read")
.clientSettings(ClientSettings.builder().requireProofKey(true).build())
.build();
}Cost and verification
PKCE adds a verifier and challenge per authorization attempt, with negligible compute cost. Server-side clients must store and rotate credentials; public clients cannot solve secret storage by hiding a value in JavaScript.
Common Mistakes
- Do not ship a confidential-client secret in browser code.
- Do not treat a client-credentials token as proof of a human user's identity.
- Do not use redirect wildcards or skip the verifier failure test.
Read next
Spring Authorization Server JDBC state: clients, grants and consent across restarts, Spring Security OAuth2 login: callback, identity and browser session, Spring OAuth2 client credentials: acquire and reuse a service token, Spring resource server JWT: validate both issuer and intended audience.
