Setting Up the Server

Dependency

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

Create a separate Spring Boot app, shopease-auth, running on port 9000. Keep it separate from your APIs: it holds the private signing key.

Configuration

package in.shopease.auth;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
// ... org.springframework imports

@Configuration
@EnableWebSecurity
public class AuthServerConfig {

    // 1. The OAuth2 / OIDC protocol endpoints
    @Bean
    @Order(1)
    SecurityFilterChain authorizationServerChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();
        http
            .securityMatcher(authorizationServer.getEndpointsMatcher())
            .with(authorizationServer, as -> as.oidc(Customizer.withDefaults()))   // enable OpenID Connect
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .exceptionHandling(ex -> ex.defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"),             // browsers go to the login page
                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)));
        return http.build();
    }

    // 2. Everything else: the login page itself
    @Bean
    @Order(2)
    SecurityFilterChain defaultChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/css/**").permitAll()
                .anyRequest().authenticated())
            .formLogin(form -> form.loginPage("/login"));
        return http.build();
    }

    // 3. Users (in production: a JPA-backed UserDetailsService, see the basic level)
    @Bean
    UserDetailsService users(PasswordEncoder encoder) {
        return new InMemoryUserDetailsManager(
            User.withUsername("asha").password(encoder.encode("asha-dev-pass")).roles("CUSTOMER").build(),
            User.withUsername("meera").password(encoder.encode("meera-dev-pass")).roles("ADMIN").build());
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    // 4. Signing key (in production: load from a keystore / secret manager, not generated at startup)
    @Bean
    JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048);
        KeyPair keyPair = generator.generateKeyPair();
        RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
                .privateKey((RSAPrivateKey) keyPair.getPrivate())
                .keyID("shopease-2026-03")
                .build();
        return new ImmutableJWKSet<>(new JWKSet(rsaKey));
    }

    // 5. The issuer value that appears in every token's "iss" claim
    @Bean
    AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().issuer("http://localhost:9000").build();
    }
}

Note: SecurityContext in JWKSource<SecurityContext> is com.nimbusds.jose.proc.SecurityContext, not Spring's. The OAuth2AuthorizationServerConfigurer.authorizationServer() style is for Spring Authorization Server 1.4+ (Spring Boot 3.4+); older guides call OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http).

Check It Runs

Discovery

curl -s http://localhost:9000/.well-known/openid-configuration | jq "{issuer, authorization_endpoint, token_endpoint, jwks_uri, grant_types_supported}"

Example output

{
  "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",
  "grant_types_supported": [
    "authorization_code",
    "client_credentials",
    "refresh_token",
    "urn:ietf:params:oauth:grant-type:device_code",
    "urn:ietf:params:oauth:grant-type:token-exchange"
  ]
}