Description
Preface
- I asked this question on Stack Overflow but I would like to encourage enhancing the Spring documentation for such a use case.
- I also read this comment in OAuth2AuthorizationRequestRedirectFilter redirect should support Ajax request #6638 as it looks quite similar to what I'm trying to achieve but I'm not sure if this is really the case.
I'm trying to setup a proof-of-concept like in the following diagram:
- SPA is a VueJS application.
- Gateway and API service are Spring Boot applications (version 2.6.3).
- OAuth2 provider is Keycloak.
Observations
Scenario 1 (browser without SPA)
When I use the browser without the SPA, then the following workflow works as intended:
- Browser hits
http://localhost:8555/api/messages
. - Gateway redirects to Keycloak login form.
- After successful authentication, the browser can access
http://localhost:8555/api/messages
and aSESSION
cookie is created. - The response from
/api/messages
is shown in the browser.
Scenario 2 (browser with SPA)
This is the scenario I'd like to actually realize. The observed workflow here is:
- User calls SPA on
http://localhost:8093
. - SPA fires a GET request to
http://localhost:8555/api/messages
. - The user is not redirected to the authentication provider.
Instead, there a several messages in the browser console indicating CORS issues.
Console
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=my-client&state=vpgnHaxh-5nb_LxksYL1a-PRU0tzuHR5itVvw1ovD-E%3D&redirect_uri=http://localhost:8555/login/oauth2/code/keycloak. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
Network Traffic
As far as I can see, the following is happening:
- GET to
http://localhost:8555/api/messages
is answered with a 302 and location/oauth2/authorization/keycloak
. - Another GET to
http://localhost:8555/oauth2/authorization/keycloak
is answered with a 302 andLocation: http://localhost:8080/auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=my-client&state=vpgnHaxh-5nb_LxksYL1a-PRU0tzuHR5itVvw1ovD-E%3D&redirect_uri=http://localhost:8555/login/oauth2/code/keycloak
- GET to
http://localhost:8080/auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=my-client&state=vpgnHaxh-5nb_LxksYL1a-PRU0tzuHR5itVvw1ovD-E%3D&redirect_uri=http%3A%2F%2Flocalhost%3A8555%2Flogin%2Foauth2%2Fcode%2Fkeycloak
is answered with 200.
But all of them have CORS issues.
Spring Boot Implementation
Gateway
application.yml
server:
port: 8555
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://localhost:8080/auth/realms/demo
user-name-attribute: preferred_username
registration:
keycloak:
provider: keycloak
client-id: my-client
client-secret: my-secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
cloud:
gateway:
default-filters:
- TokenRelay
- RemoveRequestHeader=Cookie
routes:
- id: api-service
uri: http://localhost:8092
predicates:
- Path=/api/**
SecurityConfiguration
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {
httpSecurity
.csrf().disable()
.oauth2Login()
.and()
.authorizeExchange().anyExchange().authenticated();
return httpSecurity.build();
}
}