I have an application with Vue.js in the front end and Spring Boot for the back end. We use OKTA for security. This has been working great with Java 11 and Spring Boot 2.1.8.
The Spring Boot REST services are http://localhost:7801 and the NGINX server for the UI is http://localhost:7800.
Recently, I am attempting to upgrade the back end to use Spring Boot 3.1.3 and Java 17. I get the following error when the UI accesses an end point:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:7801/oauth2/authorization/okta. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 403.
After updating Spring Boot, there were some required changes to Spring Security. Otherwise the back end code is identical. I do have http://localhost:7800 and http://localhost:7800/ configured in OKTA as Trusted Origins.
After researching this I am still not sure how to make this work. I appreciate any ideas.
Spring Boot 2.1.8
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
.and().oauth2Client()
.and().oauth2Login();
}
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/api/myapp/**")
.allowedMethods("GET","POST","PUT","DELETE")
.allowedOrigins("http://localhost:7800");
}
};
}
Spring Boot 3.1.3
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class OAuthSecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/actuator/**").permitAll()
.anyRequest().authenticated())
.oauth2Client(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults());
// done
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:7800"));
configuration.setAllowedMethods(List.of("GET","POST","PUT","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/myapp/**", configuration);
return source;
} // end corsConfigurationSource()
Request Headers
OPTIONS /oauth2/authorization/okta HTTP/1.1
Host: localhost:7801
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Referer: http://localhost:7800/
Origin: http://localhost:7800
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Response Headers
HTTP/1.1 403
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Transfer-Encoding: chunked
Date: Thu, 07 Sep 2023 12:45:00 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Console
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:7801/oauth2/authorization/okta. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 403.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:7801/oauth2/authorization/okta. (Reason: CORS request did not succeed). Status code: (null).
2
Answers
You might try defining a
FilterRegistrationBean
like we did in our Build a Simple CRUD App with Spring Boot and Vue.js tutorial.We use something similar in JHipster.
FWIW, you don’t need
@EnableWebSecurity
on your security configuration class if you’re using Spring Boot. You only need@Configuration
.Apparently, your conf allows cross origin requests to
/api/myapp/**
only and the failing request happens to/oauth2/authorization/okta
Also, instead of:
try:
this will:
corsConfigurationSource
bean you already defined and explicitly inject it in your security filter-chainWith this modified CORS configuration source
@Bean
:This will:
HEAD
andOPTIONS
requests too (and make your intentions clearer, probably).P.S. n°1
Seems that you forgot to
permitAll()
access to/login/**
and/oauth2/**
which are used during the login process.P.S. n°2
OAuth2 clients with
oauth2Login
must be protected against CSRF attacks: requests authorization is based on sessions which are the vector for CSRF attacks. Do not disable CSRF.P.S. n°3
If you were using
spring-cloud-gateway
in front of both your Vue.js app and your Spring API, all requests would have the same origin: the gateway => no more CORS config needed.The other benefit would be that you could configure the REST API as a stateless resource server (with CSRF protection disabled) while still using a "confidential" OAuth2 client and keep tokens on your servers only. I have a tutorial for configuring
spring-cloud-gateway
as such a BFF there. The frontend is written with Angular, but as all that is required regarding authorization is settingwindow.location.href
, you should be able to port it to Vue.This makes me strongly recommand you do introduce a BFF between your Vue.js app and your REST API:
spring-cloud-gateway
as stateful OAuth2 client with login and CSRF protection andTokenRelay
filterThe layer with sessions will be thinner and your overall architecture will scale much better.