0

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.html to bring me back to the swagger UI page
  • Set-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)
            }
        }
    }
}
2
  • 1
    This question is similar to: CSRF Support in springdoc-openapi swagger-ui. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Nov 9, 2024 at 18:59
  • I've edited my question, hopefully my problem is a little more clear now. I'm not having any issues with Swagger, Swagger is actually working just fine for me - as long as I take my browser to the /logout page 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. Commented Nov 9, 2024 at 19:23

1 Answer 1

1

You might need to explicitly subscribe to the CSRF cookie by exposing a csrfCookieWebFilter bean as described there: https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework

@Bean
WebFilter csrfCookieWebFilter() {
    return (exchange, chain) -> {
        Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
        return csrfToken.doOnSuccess(token -> {
            /* Ensures the token is subscribed to. */
        }).then(chain.filter(exchange));
    };
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! That seems to have done the trick! It isn't adding the X-CSRF cookie to responses during the login process, however it is adding the cookie to responses to normal GET requests to the API. I can work with that, and I can play around with inserting this same code into a provider or modified auth filter perhaps if I want to force the cookie into the API responses during the Oauth2 login.
As a side note, my starter registers such a WebFilter in reactive applications with oauth2Login when com.c4-soft.springaddons.oidc.client.csrf=cookie-accessible-from-js (unless a csrfCookieWebFilter bean is explicitly defined in application code).
Another note, as described in this article I wrote - and which you should probably read if intending to use a single page application as frontend - I send a GET request before initiating the login to fetch the "login options" offered by the Spring backend. So, the CSRF cookie is set even before the login is initiated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.