Course topics

By WebNest Studio

Spring Boot Tutorial

Spring Authorization Server

As soon as you have several applications — a web front end, a mobile app, a few microservices — you do not want each one to handle passwords and issue its own tokens. You want one central authorization server that authenticates users and issues OAuth2 access tokens and OpenID Connect ID tokens, while every API simply validates those tokens.

Spring Authorization Server is the Spring team's implementation of that server. From Spring Security 7 it ships as part of Spring Security itself, and Spring Boot 4 configures it with spring-boot-starter-security-oauth2-authorization-server. This lesson builds a working authorization server, registers clients for user login and machine-to-machine access, and connects a resource server to it.

Roles in an OAuth2 System

Four parties take part in OAuth2, and it helps to name them precisely.

  • Resource owner — the user who owns the data.
  • Client — the application asking for access (a web app, SPA backend, mobile app or service).
  • Authorization server — authenticates the user, asks for consent and issues tokens. This is what you build in this lesson.
  • Resource server — the API that accepts access tokens (see the JWT resource server lesson).

Which Grant Types to Use

OAuth 2.1 and current best practice reduce the choice to a few flows. The authorization_code grant with PKCE is for anything involving a user (web, SPA via a backend-for-frontend, mobile). client_credentials is for service-to-service calls with no user. refresh_token renews access tokens. The old implicit and password grants are removed from Spring Security 7 and should not be used.

Endpoints the Server Exposes

Once running, the server publishes standard endpoints that clients and resource servers discover automatically through /.well-known/openid-configuration:

  • /oauth2/authorize — starts the user login/consent flow.
  • /oauth2/token — exchanges codes, client credentials or refresh tokens for tokens.
  • /oauth2/jwks — public keys resource servers use to verify token signatures.
  • /oauth2/introspect and /oauth2/revoke — token introspection and revocation.
  • /userinfo — OpenID Connect user information.

Registered Clients and Persistence

Each client application is a RegisteredClient with a client id, a hashed secret, allowed grant types, redirect URIs, scopes and token settings. For demos you can declare them in application.yml and Spring Boot builds an InMemoryRegisteredClientRepository. In production, use JdbcRegisteredClientRepository, JdbcOAuth2AuthorizationService and JdbcOAuth2AuthorizationConsentService so clients, issued tokens and consents survive restarts and scale across instances. Keep the signing keys stable across restarts too — if keys change, every issued token becomes invalid.

Customising Token Claims

An OAuth2TokenCustomizer<JwtEncodingContext> bean lets you add claims to access or ID tokens — for example the user's roles, tenant id or subscription plan — so resource servers can make authorization decisions without calling back to the user database.

Examples

Authorization server with clients declared in configuration

Java
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security-oauth2-authorization-server</artifactId>
</dependency>

# application.yml of auth-server (port 9000)
server:
  port: 9000
spring:
  security:
    user:
      name: asha
      password: "{noop}user123"      # demo only — use a real UserDetailsService
    oauth2:
      authorizationserver:
        issuer: http://localhost:9000
        client:
          web-app:
            registration:
              client-id: web-app
              client-secret: "{bcrypt}$2a$10$6hN5E3sQ8kK6f6YwQ2iY6eJ0b5n0m2J0yJ3dO6oQ3bHk8lE9uF0bK"
              client-authentication-methods: client_secret_basic
              authorization-grant-types: authorization_code, refresh_token
              redirect-uris: http://localhost:8080/login/oauth2/code/webnest
              scopes: openid, profile, orders.read
            require-authorization-consent: true
            require-proof-key: true
          reporting-service:
            registration:
              client-id: reporting-service
              client-secret: "{bcrypt}$2a$10$Vd1Wm9aYxJgYv6k1v0k5mO2nK7n8V1a0cS6oR2wQ1bT9uZ4pA3hXe"
              client-authentication-methods: client_secret_basic
              authorization-grant-types: client_credentials
              scopes: orders.read
Output
GET http://localhost:9000/.well-known/openid-configuration
{"issuer":"http://localhost:9000","authorization_endpoint":"http://localhost:9000/oauth2/authorize","token_endpoint":"http://localhost:9000/oauth2/token","jwks_uri":"http://localhost:9000/oauth2/jwks", ...}

Machine-to-machine token with client_credentials

Java
curl -s -u reporting-service:reporting-secret \
     -d "grant_type=client_credentials&scope=orders.read" \
     http://localhost:9000/oauth2/token
Output
{"access_token":"eyJraWQiOiI4YjY...","scope":"orders.read","token_type":"Bearer","expires_in":299}

Adding roles to access tokens and persisting clients in the database

Java
@Configuration
public class AuthServerConfig {

    // Put the user's roles into every access token
    @Bean
    OAuth2TokenCustomizer<JwtEncodingContext> rolesClaim() {
        return context -> {
            if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())
                    && context.getPrincipal() != null) {
                Set<String> roles = context.getPrincipal().getAuthorities().stream()
                    .map(GrantedAuthority::getAuthority)
                    .filter(a -> a.startsWith("ROLE_"))
                    .map(a -> a.substring(5))
                    .collect(Collectors.toSet());
                context.getClaims().claim("roles", roles);
            }
        };
    }

    // Production: store clients, authorizations and consents in the database
    @Bean
    RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbc) {
        return new JdbcRegisteredClientRepository(jdbc);
    }

    @Bean
    OAuth2AuthorizationService authorizationService(JdbcTemplate jdbc,
                                                    RegisteredClientRepository clients) {
        return new JdbcOAuth2AuthorizationService(jdbc, clients);
    }

    @Bean
    OAuth2AuthorizationConsentService consentService(JdbcTemplate jdbc,
                                                     RegisteredClientRepository clients) {
        return new JdbcOAuth2AuthorizationConsentService(jdbc, clients);
    }
}
Output
Decoded access token payload:
{"sub":"asha","aud":"web-app","scope":["openid","orders.read"],"roles":["USER"],"iss":"http://localhost:9000","exp":1790503200}

A web app logging in through the authorization server, and an API trusting it

Java
# web-app (port 8080) — OAuth2 client
spring:
  security:
    oauth2:
      client:
        registration:
          webnest:
            provider: webnest
            client-id: web-app
            client-secret: ${WEB_APP_SECRET}
            authorization-grant-type: authorization_code
            scope: openid, profile, orders.read
        provider:
          webnest:
            issuer-uri: http://localhost:9000

# orders-api (port 8081) — resource server
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000
Output
Browser -> web-app /dashboard -> redirect to auth-server login -> consent screen -> back to web-app
web-app calls orders-api with the access token -> 200 OK (signature verified with keys from /oauth2/jwks)

Common Mistakes

  • Generating a new RSA key pair on every startup, which silently invalidates all issued tokens after each deploy.
  • Keeping registered clients and authorizations in memory in production; tokens and consents vanish on restart and do not work with more than one instance.
  • Still using the password grant, which is removed in Spring Security 7 — use authorization_code with PKCE for users.
  • Putting large or sensitive data into token claims; tokens are readable by anyone who holds them.
  • Using http:// issuer URLs outside local development — tokens and codes must only travel over HTTPS.

Key Points to Remember

  • Spring Authorization Server issues OAuth2 access tokens and OIDC ID tokens for all of your applications from one place.
  • In Boot 4 add spring-boot-starter-security-oauth2-authorization-server; clients can be declared under spring.security.oauth2.authorizationserver.client.
  • Use authorization_code + PKCE for users, client_credentials for services and refresh_token for renewal.
  • Persist clients, authorizations and consents with the JDBC implementations and keep signing keys stable.
  • OAuth2TokenCustomizer adds claims such as roles so resource servers can authorize locally.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.