Careeroza
Registering Clients
A Public SPA Client and a Service Client
@Bean
RegisteredClientRepository registeredClientRepository(PasswordEncoder encoder) {
RegisteredClient reactApp = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("shopease-web")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE) // public client, no secret
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:5173/callback")
.postLogoutRedirectUri("http://localhost:5173/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("orders.read")
.scope("orders.write")
.clientSettings(ClientSettings.builder()
.requireProofKey(true) // PKCE is mandatory
.requireAuthorizationConsent(false) // first-party app: skip consent screen
.build())
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(10))
.refreshTokenTimeToLive(Duration.ofDays(7))
.reuseRefreshTokens(false) // rotate refresh tokens
.build())
.build();
RegisteredClient orderService = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("order-service")
.clientSecret(encoder.encode(System.getenv("ORDER_SERVICE_SECRET")))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("payments.write")
.scope("inventory.reserve")
.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofMinutes(5)).build())
.build();
return new InMemoryRegisteredClientRepository(reactApp, orderService);
}Tip: In production use JdbcRegisteredClientRepository, JdbcOAuth2AuthorizationService and JdbcOAuth2AuthorizationConsentService. The SQL schema files ship inside the SAS jar under org/springframework/security/oauth2/server/authorization/. With in-memory storage, all issued refresh tokens are lost on restart.
Client Credentials in Action
Request a service token
curl -s -u order-service:$ORDER_SERVICE_SECRET \
-d grant_type=client_credentials -d scope=payments.write \
http://localhost:9000/oauth2/tokenExample output
{
"access_token": "eyJraWQiOiJzaG9wZWFzZS0yMDI2LTAzIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJvcmRlci1zZXJ2aWNlIiwiYXVkIjoib3JkZXItc2VydmljZSIsIm5iZiI6MTc3MjM0MDAwMCwic2NvcGUiOlsicGF5bWVudHMud3JpdGUiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwiZXhwIjoxNzcyMzQwMzAwLCJpYXQiOjE3NzIzNDAwMDB9.Qm9...",
"scope": "payments.write",
"token_type": "Bearer",
"expires_in": 299
}Properties-Only Registration
application.yml
Spring Boot can also register clients from configuration, which is handy for small setups:
spring:
security:
oauth2:
authorizationserver:
issuer: http://localhost:9000
client:
shopease-web:
registration:
client-id: shopease-web
client-authentication-methods: none
authorization-grant-types: authorization_code, refresh_token
redirect-uris: http://localhost:5173/callback
scopes: openid, profile, orders.read, orders.write
require-proof-key: true