Start Learning
Javaneer
Back to stage
Module 12·Spring Authorization Server

Service-to-Service & Federation

The client-credentials grant for machine-to-machine calls, federated (social) login, and pointing BookVault's resource server at your own issuer.

14 min readAdvanced
On this page

Two flows remain, and both matter in real systems. Client credentials handles the case with no user at all - one service calling another. Federation lets your authorization server delegate the actual login to Google or your corporate identity provider, so you issue your tokens while users authenticate elsewhere. Finally, we point BookVault's resource server at your issuer, retiring the HS256 shortcut for good.

Client credentials: machine-to-machine

When there's no human - a nightly reporting job, a service calling another service - there's no login to run. The client credentials grant has the service authenticate as itself with its own ID and secret, and get back an access token:

POST /oauth2/token
  grant_type=client_credentials
  client_id=bookvault-reporting&client_secret=...
  scope=loans:read
→  { "access_token": "...", "expires_in": 300 }

No user, no redirect, no PKCE - just a confidential client proving itself. Register it with the client- credentials grant and only the scopes that job needs:

RegisteredClient reporting = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("bookvault-reporting")
    .clientSecret(encoder.encode("{the-secret}"))
    .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
    .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
    .scope("loans:read")                       // least privilege
    .build();

The resulting token has no user subject - it represents the service. So authorize on scopes (loans:read), not on user roles. Give each service its own client and the narrowest scopes; a leaked reporting credential should never be able to write loans.

Federation: delegating the login

Your authorization server doesn't have to store passwords. With federated identity, it acts as a client to an upstream provider (Google, GitHub, a corporate OIDC/SAML IdP): the user logs in there, and your server still issues your tokens to your clients. This is how "Sign in with Google" coexists with your own token format.

// The auth server itself becomes an OAuth2 client of Google for the login step
@Bean
SecurityFilterChain login(HttpSecurity http) throws Exception {
    return http
        .formLogin(withDefaults())                 // local username/password, and/or...
        .oauth2Login(withDefaults())               // ...federated login via an upstream IdP
        .build();
}

The payoff: one issuer, many login methods. BookVault apps only ever trust your tokens and know your JWKS - whether the human authenticated with a password, Google, or a company SSO is an internal detail. Add or swap identity providers without touching a single resource server.

Retiring the HS256 shortcut

Now BookVault's API stops being its own tiny token factory and becomes a proper resource server trusting your issuer. The whole change is configuration - point it at the issuer's discovery document:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.bookvault.dev   # fetches JWKS + validates iss/exp/aud

Spring auto-discovers /.well-known/openid-configuration, downloads the public keys from /oauth2/jwks (cached, auto-rotating), and validates every incoming JWT's signature, issuer, and expiry - locally, no shared secret. BookVault authorizes on the token's scopes/claims exactly as before, but the tokens are now issued, signed, and revocable by a real authorization server that other apps can trust too.

Scopes for services, roles for users - keep them distinct

A client-credentials token has no user, so don't reuse user-role logic on it. Model service permissions as scopes (loans:read, catalog:write) and user permissions as roles/authorities, and authorize each on the right axis. Blurring them tends to grant services human-level access - or force awkward fake 'service users'.

Staff keycards vs. a visitor badge issued from a partner's ID

Client credentials is a staff keycard: it isn't tied to a guest, it just proves 'this machine is our reporting service' and opens exactly the doors that role allows. Federation is the front desk issuing your building's visitor badge after checking the visitor's passport from another country - they authenticated abroad, but inside your building everyone reads your badge, not their foreign passport. Retiring HS256 is finally having every door read badges from the one official badge office instead of each door printing its own stick-on labels that any door could also forge.

Design the access for a new integration

A partner library wants its backend to pull BookVault's public catalog nightly (no end users involved), and separately, BookVault wants to let members sign in with their Google accounts. Which grant/mechanism covers each, what scopes/claims would you issue, and does BookVault's resource server config change for either?

Which grant does a backend service use to call another API when no user is involved, and how should you authorize it?

Key takeaways

  • Client credentials is the no-user, machine-to-machine grant: a confidential client authenticates as itself for a token with no user subject - authorize on scopes, least privilege.
  • Federation lets your authorization server delegate login to an upstream IdP (Google, corporate SSO) while still issuing your own tokens - one issuer, many login methods.
  • Federation means resource servers trust only your issuer/JWKS; how the human actually authenticated is an internal detail you can change freely.
  • Retire HS256 by making BookVault a resource server with just issuer-uri: it auto-discovers JWKS and validates signature, issuer, and expiry locally.
  • Keep service permissions (scopes) and user permissions (roles) on separate axes so services never inherit human-level access.
Was this lesson helpful?
Edit this page on GitHub