angularspring-boot

spring boot 3 oauth2 origin has been blocked by CORS policy in Angular 16


I have problem to authentication with spring boot 3 and angular 16. I develop an Authorization Server and get access token with "authorization_code" grand type in postman successfully. but it dose not worked in angular 16 project.

The Authorization server configuuration:

@Configuration
@EnableWebSecurity
public class AuthorizationServerConfig {

    private final UserRepository userRepository;
    private final ClientRepository clientRepository;

    public AuthorizationServerConfig(UserRepository userRepository, ClientRepository clientRepository) {
        this.userRepository = userRepository;
        this.clientRepository = clientRepository;
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://localhost:8089")
                .build();
    }

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.cors(Customizer.withDefaults());

        http.exceptionHandling(exceptions -> exceptions
                .defaultAuthenticationEntryPointFor(
                        new LoginUrlAuthenticationEntryPoint("/login"),
                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                )
        );

        http.oauth2ResourceServer(resourceServer -> resourceServer
                .jwt(Customizer.withDefaults())
        );

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());

        return http.build();
    }


    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/.well-known/*").permitAll()
                    .requestMatchers("/login").permitAll()
                    .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());

        return http.build();
    }


    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer() {
        return context -> {
            if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
                String username = context.getPrincipal().getName();
                UserEntity user = userRepository.findByName(username)
                        .orElseThrow(() -> new UsernameNotFoundException("User not found"));

                Set<String> roles = new HashSet<>();
                for (RoleEntity role: user.getRoles())
                    roles.add(role.getName());

                context.getClaims().claim("username", username);
                context.getClaims().claim("roles", roles);

                // find branch
                String branchId = "";
                context.getClaims().claim("organization", branchId);
            }
        };
    }

    @Bean
    @Primary
    public RegisteredClientRepository registeredClientRepository() {
        List<RegisteredClient> registeredClients = new ArrayList<>();

        List<ClientEntity> clientEntities = clientRepository.findAll();
        for (ClientEntity clientEntity : clientEntities)
            registeredClients.add(clientEntity.maptoRegisteredClient());

        registeredClients.add(publicClientPKCE());

        return new InMemoryRegisteredClientRepository(registeredClients);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    private RegisteredClient publicClientPKCE() {
        return RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("public-client")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://127.0.0.1:4200")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(false)
                        .requireProofKey(true) // PKCE required
                        .build()
                )
                .build();
    }

}

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://127.0.0.1:4200"); 
        config.addAllowedOrigin("http://localhost:4200");
        config.addAllowedHeader("*"); 
        config.addAllowedMethod("*"); 
        config.setAllowCredentials(true);

        source.registerCorsConfiguration("/**", config);
        source.registerCorsConfiguration("/.well-known/**", config);
        return source;
    }
}

and angular configuration like this:

    export const authConfig: AuthConfig = {
  issuer: 'http://localhost:8089', // URL of the Authorization Server
  redirectUri: window.location.origin, // Redirect URI
  clientId: 'public-client', // Client ID for the Angular app
  responseType: 'code', // Authorization Code Flow
  scope: 'openid profile', // Scopes requested
  showDebugInformation: true,
  useSilentRefresh: false, // To silently refresh tokens
  silentRefreshTimeout: 5000,
  sessionChecksEnabled: true,
  timeoutFactor: 0.75,
  useHttpBasicAuth: false,
  disableAtHashCheck: true,
  strictDiscoveryDocumentValidation: false
};


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  constructor(private oauthService: OAuthService, private router: Router) {
    this.oauthService.configure(authConfig);
  }

  ngOnInit(): void {
    this.oauthService
      .loadDiscoveryDocumentAndTryLogin()
      .then(() => {
        debugger;
        if (this.oauthService.hasValidAccessToken()) {
          console.log('Logged in successfully');
          this.router.navigate(['test']);
        } else {
          this.oauthService.initLoginFlow();
        }
      })
      .catch((error) => {
        debugger
        console.error('Error during login:', error);
      });

  }

}

browser console error:

Access to XMLHttpRequest at 'http://localhost:8089/.well- 
known/openid-configuration' from origin 'http://127.0.0.1:4200' 
has been blocked by CORS policy: Response to preflight request 
doesn't pass access control...

Solution

  • I believe requests to .well-known is processed by the second SecurityFilterChain, hence you'll need to add .cors(Customizer.withDefaults()) there as well.

        @Bean
        @Order(2)
        public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            http
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/.well-known/*").permitAll()
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults());
    
            return http.build();
        }