Reputation: 190
We have two filter chains (beans) configured in our SecurityConfig
:
oauth2ProtocolEndpointsSecurityFilterChain
with order 1 (authServer config)userEndpointsSecurityFilterChain
with order 2 (config for external OIDC IdPs)The second chain configures external OIDC providers, so when a user tries to log in to an app using our Auth server, they are forwarded to an external OIDC provider for authentication). This worked great until we changed response_mode
to form_post
in our custom AuthorizationRequestResolver
(query
is default, but is less secure):
additionalParameters.put(RESPONSE_MODE, "form_post");
This is now what happens. When the external OIDC provider redirects back to our auth server with the code value, we're getting an access denied exception. This is what I find in my logs with trace level enabled:
{"@timestamp":"2025-01-03T14:16:11.229589993+01:00","@version":"1","message":"Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=10.131.0.2, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied","logger_name":"org.springframework.security.web.access.ExceptionTranslationFilter","thread_name":"http-nio-8080-exec-4","level":"TRACE","level_value":5000,"stack_trace":"org.springframework.security.authorization.AuthorizationDeniedException: Access Denied\n\tat org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:99)\n\tat
Below this error I'm finding something I think is the root of the problem – a csrf requirement:
Invalid CSRF token found for https://nettskjema-authorization-dev.uio.no/login/oauth2/code/idporten
I have tried to disable csrf, just to figure out what happens. This have no affect in the second bean (filter) of course, but the first one. So I'm replacing the following config with another one, with CSRF completely disabled:
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.csrf(csrf -> csrf.disable())
But the invalid CSRF token error is still there.
I have debugged the traffic by using DevTools, so this is the post request that is sent back from our external IdP (Curl to simplify)
curl 'https://nettskjema-authorization-dev.uio.no/login/oauth2/code/idporten' --compressed -X POST -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: no,en;q=0.7,en-US;q=0.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: https://login.test.idporten.no' -H 'DNT: 1' -H 'Sec-GPC: 1' -H 'Connection: keep-alive' -H 'Referer: https://login.test.idporten.no/' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-Fetch-Dest: document' -H 'Sec-Fetch-Mode: navigate' -H 'Sec-Fetch-Site: cross-site' -H 'Priority: u=0, i' --data-raw 'iss=https%3A%2F%2Ftest.idporten.no&code=******&state=*******'
I find this very strange. In another Spring application (not Spring Auth Server), we're not having any issues with response_mode=form_post
. But this does not work with an OIDC client registered in Spring Auth Server.
Upvotes: 1
Views: 142
Reputation: 190
I figured it out! form_post
does not work because we had configured the following bean:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite(LAX.attributeValue());
return serializer;
}
SameSite
must be NONE
when response_mode=form_post
is being used, something I read about here:
https://docs.feide.no/general/faq/samesite_cookie.html#changes-that-need-to-be-made-on-the-service-provider-side
So I added the following in application.properties
since we're running on Spring Boot:
server.servlet.session.cookie.same-site="none"
I also had to add this to the second filter chain, something similar what is done with authorizationServerConfigurer.getEndpointsMatcher()
:
.csrf(csrf -> csrf.ignoringRequestMatchers("/login/oauth2/**"))
Upvotes: 0
Reputation: 190
It seems that we're not directly affected by this bug since it's related to response back from Spring Authorization Server and the client. This is not the case here, because we're talking about the response back from the external IdP and the auth server.
I tested the feature request issue, and it works. But it does not solve our problem. As mentioned, we have two security filter chains configured – one for the server config and another for logging directly onto the server for administering Oauth2 clients. The latter is not using LDAP (username/password), but OIDC. So this is the configuration:
@Bean
@Order(2)
public SecurityFilterChain userEndpointsSecurityFilterChain(final HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName(null);
SessionRegistry registry = redisSessionRegistry != null ?
redisSessionRegistry :
sessionRegistry;
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(WHITELIST).permitAll()
.anyRequest().authenticated())
.requestCache(cache -> cache
.requestCache(requestCache))
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessHandler(oidcLogoutSuccessHandler())
.addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(CACHE, COOKIES)))
.invalidateHttpSession(true))
.headers(headers -> headers
.httpStrictTransportSecurity(
hsts -> hsts
.includeSubDomains(true)
.preload(true)
.maxAgeInSeconds(31536000))
.frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicy.SAME_ORIGIN))
.permissionsPolicy(permissions -> permissions.policy(
"clipboard-write=(self)")))
.oauth2Login(oauth2Login -> oauth2Login
.loginPage("/")
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.authorizationRequestResolver(authorizationRequestResolver()))
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(authorizationOidcUserService.getOidcUserService())))
.sessionManagement(session -> session
.maximumSessions(1)
.sessionRegistry(registry))
.csrf(csrf -> csrf.disable());
return http.build();
}
This is the relevant config in AuthorizationRequestResolver
to enable response_mode=form_post
: additionalParameters.put(RESPONSE_MODE, "form_post");
The strange thing is that it works if I run this on localhost, but not on Openshift. I have tried to disable Redis as well and run the application by using only one pod, but I'm stuck with the error [authorization_request_not_found]
when I'm sent back from the external IdP and to our Spring Authorization Server.
Upvotes: 0
Reputation: 11134
Update Out of curiosity, I attempted to replicate the error using a mock OAuth provider on top of this sample authorization server.
The mock OAuth provider consisted of a @RestController with /token and /userinfo endpoints, along with a @Controller that served a Thymeleaf template containing a form to post the code and state to the /login/oauth2/code endpoint.
To my surprise, this setup worked as expected. The only change I needed to make in the default security chain regarding csrf, was adding .csrf(csrf -> csrf.ignoringRequestMatchers("/mock-oauth/token"))
.
Tampering with the state in the form results in "Invalid username/password" and no "Changed session id ..." in logs.
Log Excerpt
Authorization server on port 9000, demo-client on port 8080.
2025-01-28T12:35:59.326+01:00 DEBUG 311706 --- [nio-9000-exec-1] o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorization/fake-idp
2025-01-28T12:35:59.333+01:00 DEBUG 311706 --- [nio-9000-exec-1] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://localhost:9000/mock-oauth/authorize?response_mode=form_post&response_type=code&client_id=fake-client-id&state=VniWwrXEDKxkTmFz0i47HlglZpYCHlnNwkHZAH9F_tY%3D&redirect_uri=http://localhost:9000/login/oauth2/code/fake-idp
2025-01-28T12:35:59.337+01:00 DEBUG 311706 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Securing GET /mock-oauth/authorize?response_mode=form_post&response_type=code&client_id=fake-client-id&state=VniWwrXEDKxkTmFz0i47HlglZpYCHlnNwkHZAH9F_tY%3D&redirect_uri=http://localhost:9000/login/oauth2/code/fake-idp
2025-01-28T12:35:59.337+01:00 DEBUG 311706 --- [nio-9000-exec-2] o.s.security.web.FilterChainProxy : Secured GET /mock-oauth/authorize?response_mode=form_post&response_type=code&client_id=fake-client-id&state=VniWwrXEDKxkTmFz0i47HlglZpYCHlnNwkHZAH9F_tY%3D&redirect_uri=http://localhost:9000/login/oauth2/code/fake-idp
2025-01-28T12:35:59.352+01:00 DEBUG 311706 --- [nio-9000-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2025-01-28T12:35:59.369+01:00 DEBUG 311706 --- [nio-9000-exec-3] o.s.security.web.FilterChainProxy : Securing POST /login/oauth2/code/fake-idp
2025-01-28T12:35:59.388+01:00 DEBUG 311706 --- [nio-9000-exec-4] o.s.security.web.FilterChainProxy : Securing POST /mock-oauth/token
2025-01-28T12:35:59.388+01:00 DEBUG 311706 --- [nio-9000-exec-4] o.s.security.web.FilterChainProxy : Secured POST /mock-oauth/token
2025-01-28T12:35:59.393+01:00 DEBUG 311706 --- [nio-9000-exec-4] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2025-01-28T12:35:59.407+01:00 DEBUG 311706 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy : Securing GET /mock-oauth/userinfo
2025-01-28T12:35:59.408+01:00 DEBUG 311706 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy : Secured GET /mock-oauth/userinfo
2025-01-28T12:35:59.409+01:00 DEBUG 311706 --- [nio-9000-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2025-01-28T12:35:59.412+01:00 DEBUG 311706 --- [nio-9000-exec-3] o.s.s.w.s.HttpSessionEventPublisher : Publishing event: org.springframework.security.web.session.HttpSessionIdChangedEvent[source=org.apache.catalina.session.StandardSessionFacade@797520bb]
2025-01-28T12:35:59.412+01:00 DEBUG 311706 --- [nio-9000-exec-3] .s.ChangeSessionIdAuthenticationStrategy : Changed session id from DD08156AD378F7B7A42126AE667555F7
2025-01-28T12:35:59.413+01:00 DEBUG 311706 --- [nio-9000-exec-3] o.s.s.w.csrf.CsrfAuthenticationStrategy : Replaced CSRF Token
2025-01-28T12:35:59.413+01:00 DEBUG 311706 --- [nio-9000-exec-3] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [john-doe], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{name=John Doe, [email protected], sub=john-doe}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=DD08156AD378F7B7A42126AE667555F7], Granted Authorities=[OAUTH2_USER]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@797520bb]
2025-01-28T12:35:59.413+01:00 DEBUG 311706 --- [nio-9000-exec-3] .s.o.c.w.OAuth2LoginAuthenticationFilter : Set SecurityContextHolder to OAuth2AuthenticationToken [Principal=Name: [john-doe], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{name=John Doe, [email protected], sub=john-doe}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=DD08156AD378F7B7A42126AE667555F7], Granted Authorities=[OAUTH2_USER]]
2025-01-28T12:35:59.413+01:00 INFO 311706 --- [nio-9000-exec-3] atedIdentityAuthenticationSuccessHandler : Inside FederatedIdentityAuthenticationSuccessHandler#onAuthenticationSuccess
2025-01-28T12:35:59.413+01:00 DEBUG 311706 --- [nio-9000-exec-3] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://localhost:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid&state=5JsJN1WarqEsD3b4mGPtInBK-mID50jgYjeMfDBvHpI%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=QS1Epo7JGtxQ0EfsxqNN8jDnJSPRL-GK4tBNt6MGpCs&continue
2025-01-28T12:35:59.417+01:00 DEBUG 311706 --- [nio-9000-exec-6] o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid&state=5JsJN1WarqEsD3b4mGPtInBK-mID50jgYjeMfDBvHpI%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=QS1Epo7JGtxQ0EfsxqNN8jDnJSPRL-GK4tBNt6MGpCs&continue
2025-01-28T12:35:59.417+01:00 DEBUG 311706 --- [nio-9000-exec-6] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [john-doe], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{name=John Doe, [email protected], sub=john-doe}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=DD08156AD378F7B7A42126AE667555F7], Granted Authorities=[OAUTH2_USER]]]
2025-01-28T12:35:59.439+01:00 DEBUG 311706 --- [nio-9000-exec-6] o.s.s.core.session.SessionRegistryImpl : Registering session 90B8C3E6609295C36D0EC77F685E8729, for principal Name: [john-doe], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{name=John Doe, [email protected], sub=john-doe}]
2025-01-28T12:35:59.440+01:00 DEBUG 311706 --- [nio-9000-exec-6] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=q_rdPkm03QlDWMJP1dUKkmcogxmaKU4DP2CBWFAF4AjOkpGELxdydDacXSLkrV013Tirb2XxRsxyLiopUskrj3Ak7JX7AF3OB0j1GhKSMjbMS7zloedlRmIFP5fYkeg_&state=5JsJN1WarqEsD3b4mGPtInBK-mID50jgYjeMfDBvHpI%3D
2025-01-28T12:35:59.455+01:00 DEBUG 311706 --- [nio-9000-exec-7] o.s.security.web.FilterChainProxy : Securing GET /oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid&state=dHsy-SyOzR6tT9_vnURrzX_HHfyyfbF7o8ZiPHNmQEg%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=jP7donBhc-ZWGaWepuZHCjbRLsrKQOK15MCwoRbbNL8
2025-01-28T12:35:59.456+01:00 DEBUG 311706 --- [nio-9000-exec-7] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [john-doe], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{name=John Doe, [email protected], sub=john-doe}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=DD08156AD378F7B7A42126AE667555F7], Granted Authorities=[OAUTH2_USER]]]
2025-01-28T12:35:59.460+01:00 DEBUG 311706 --- [nio-9000-exec-7] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=5d0twI71b5oOMwU5AeIL4eVfV-8A0rkfYb64vWSQTAZMbtIXuWC2kuwrERsTASo88JaDL8MfeyP83Gq16oUQxpOp1deI_NlJ6s0Th3vLEZiYl7VQR-uOxoOj9mK70AEW&state=dHsy-SyOzR6tT9_vnURrzX_HHfyyfbF7o8ZiPHNmQEg%3D
2025-01-28T12:35:59.471+01:00 DEBUG 311706 --- [nio-9000-exec-8] o.s.security.web.FilterChainProxy : Securing POST /oauth2/token
2025-01-28T12:35:59.580+01:00 DEBUG 311706 --- [nio-9000-exec-8] o.s.a.w.OAuth2ClientAuthenticationFilter : Set SecurityContextHolder authentication to OAuth2ClientAuthenticationToken
2025-01-28T12:35:59.700+01:00 DEBUG 311706 --- [io-9000-exec-10] o.s.security.web.FilterChainProxy : Securing GET /oauth2/jwks
Original answer
As the comments above suggests, Spring Security currently does not support response_mode = form_post
, but there is a feature request issue that can be upvoted.
Issue is that
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter
does not handle POST-requests, and the error OAuth2AuthenticationException: [authorization_request_not_found]
OP sees, has origin from this code starting on line 171
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
Upvotes: 1