I'm implementing a simple Sign-In via Google Oauth2 for my Spring Webflux backend and ReactJS frontend. For now I'm just trying to get the API working and test it with an OpenAPI swagger page.
I've almost got the sign-in working, except for one problem - no X-CSRF cookie is being attached to my API's response after logging into Google - it's only attaching a SESSION cookie. So GET requests to secured endpoints are working fine, because that SESSION cookie links to a valid session with the user details, however POST requests are failing because there is no JS-accessible X-CSRF cookie to attach as a header.
Specifically, the request to http://localhost:8080/login/oauth2/code/google?state=...&code=...&scope=email+profile+...... responds with a 302 status and headers:
Location: /swagger-ui.htmlto bring me back to the swagger UI pageSet-Cookie: SESSION=<session-id>because I am now logged in
But that's it, there's no Set-Cookie header for the X-CSRF token.
Confusingly, the X-CSRF token IS being attached via Set-Cookie when I go to Spring's /logout page... But that's pretty useless, I want the X-CSRF cookie attached in the same response that sets the SESSION cookie in the first place. Because at that point the User has logged in, so I want my Spring API to dish them out a X-CSRF cookie alongside the SESSION cookie.
What am I missing here in my config?
EDIT: To be clear, my swagger UI is working just fine once the X-CSRF cookie is returned to my browser. As long as I first go to the Spring /logout page, which responds with a Set-Cookie header with the X-CSRF cookie, if I go back to the Swagger UI - it will then use that cookie correctly by attaching it in a matching header.
My question is - how do I get this X-CSRF cookie attached to the same response that sets the SESSION cookie in the login process?
My SecurityConfig.kt:
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http
.csrf { csrf -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(ServerCsrfTokenRequestAttributeHandler())
}
.authorizeExchange { exchange ->
exchange
.anyExchange()
.authenticated()
}
.oauth2Login(Customizer.withDefaults())
.build()
}
}
My application.yml:
springdoc:
swagger-ui:
enabled: true
csrf:
enabled: true
header-name: "X-XSRF-TOKEN"
cookie-name: "XSRF-TOKEN"
url: /my-open-api-spec.yaml
path: /swagger-ui.html
api-docs:
enabled: true
path: /api-docs
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${google_client_id}
client-secret: ${google_client_secret}
scope:
- email
- profile
My Application.kt:
@SpringBootApplication
@EnableR2dbcAuditing
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
My HttpRouter.kt:
@Configuration
class HttpRouter(val userHandler: UserHandler) {
@Bean
fun routes(): RouterFunction<ServerResponse> {
return router {
"/users".nest {
GET("/me", userHandler::getMyUserDetails)
POST("", userHandler::inviteUser)
GET("", userHandler::searchUsers)
}
}
}
}
/logoutpage first - which attaches the X-CSRF cookie - if I then go back to the Swagger UI and launch POST requests, everything works fine. It knows to use that X-CSRF cookie and attach it as a header. My problem is - why is that X-CSRF cookie not being attached to any responses during the login process? How can I get it to be attached along with the SESSION cookie.