I have made an OAuth2 configuration in Spring Boot. It also works, especially when I make get requests via the browser URL and I am not yet authenticated, I am redirected to the IDP, I can log in and am also redirected again accordingly. I have built an endpoint for this. In addition, I use an SPA with which the login also works, provided I use an href and call the implemented route. However, if I make a call to the API via Javascript, I would actually expect an Unauthorized if no login has yet taken place. Instead, spring boot tries to forward me to the IDP, but then I get a CORS problem from the IDP. Is there any way I can set that unauthorized routes actually send an Unauthorized status instead of forwarding to the IDP? I have already tried to implement the AuthenticationEntryPoint via exceptionhandling, but then every request is answered with 401.
This is my actual code without AuthenticationEntryPoint
@Bean
SecurityFilterChain oauth2Security(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource())).csrf(csrf -> {
csrf.csrfTokenRepository(csrfTokenRepository());
csrf.csrfTokenRequestHandler(csrfTokenRequestHandler());
}).authorizeHttpRequests(
authz -> authz.requestMatchers("/api/oauth2/login").permitAll()
.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults()
);
return http.build();
}
The login route is a custom route which redirects back to my frontend, after login successfully.
I expect that the request on “/api/oauth2/login” will authenticate the user via IDP and set JSESSIONID.
All other API Calls need the JSESSIONID. If it is invalid or not set, the api should return 401.
original_1887 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
You have configured your Spring application as an OAuth2 client with oauth2Login
. This is the best option for a Gateway configured as an OAuth2 BFF (Backend For Frontend, with the TokenRelay
filter) and for server-side rendered applications (Thymeleaf, JSF, JSP, …), but not for REST APIs, especially if you’re moving toward a multi-service architecture: if all are configured with oauth2Login
, that can make a lot of different clients to authorize independently and a pretty bad user experience.
REST APIs are much better configured as resource servers: resource servers are stateless (no need for sessions nor protection against CSRF), which makes them super easy to scale.
Main differences between the security filter-chains with oauth2Login
and those with oauth2ResourceServer
:
- requests authorization is session-based for the 1st (using a cookie), and token-based for the 2nd (using a
Bearer
token). - protection against CSRF should always be enabled in a filter-chain with
oauth2Login
(it is useless withoauth2ResourceServer
) - user can “login” on a client with
oauth2Login
, but there is no notion of “login” to a resource server: being “logged in” or not is a state, and resource servers are stateless (resource servers are not concerned by the flow used to get a token, all it cares is this token to be valid and issued by an authorization server it trusts) - we usually want unauthorized requests to protected resources to be redirected to login on a client with
oauth2Login
and answered a401
on resource servers
I wrote an article on Baeldung about the OAuth2 BFF pattern. Your problem is solved as follows in it:
- REST APIs are configured as resource servers:
- with
oauth2ResourceServer
and a JWT decoder - stateless
- without protection against CSRF
- returning
401
when trying to access a protected resource with an anonymous request (or an invalid token)
- with
- a Spring Cloud Gateway is configured as an OAuth2 BFF (Backend For Frontend)
- with
oauth2Login
(and as a consequence with sessions enabled: tokens are stored in session) - with protection against CSRF (cookie-based in the case of a single-page or mobile app)
- with the
TokenRelay=
filter on routes to resource servers (replaces the session cookie with the access token in session before forwarding a request) - I chose to
permitAll()
all requests to resource servers and to handleUnauthorized
in frontends, but if some Gateway routes were defined asauthenticated()
, then redirection to login would be handled by Spring.
- with
- regarding the CORS error when following the redirection to the authorization endpoint, I solved it by changing the origin: what we want to do is to temprarly exist the Javascript based app and go to the authorization server login UI, not to send a cross origin REST request. I do it by setting the
windows.location.href
in Javascript code instead of just letting the browser follow to the302
location. But for the redirection to be intercepted by the Javascript code, the status must be changed from302
to something in the2xx
range in the Spring client.
In this configuration, the only OAuth2 client with oauth2Login
to authorize is the gateway, whatever the number of services behind it.
Another option is to configure the frontend as a public OAuth2 client (with a proper lib), but this is now discouraged. In this configuration, you don’t need a gateway. But when using one to have the same origin for all resource servers, security would be completely disabled on the gateway:
- login, token storage, and requests authorization with a Bearer token are handled by the frontend
- each REST API is configured as resource server