Search code examples
google-chromehttp-headerscorssession-cookiespreflight

How do I instruct a browser to include a cookie in requests after the window has been refreshed?


I have a Spring-Boot back-end and a React front-end (written in Typescript and using the Axios HTTP client). Each of these applications is hosted on a different sub-domain of a common parent-domain.

I am attempting to use cookie-based authentication for the back-end and running into the following issue:

  1. A login request is made by the front-end and the back-end returns a response with a "Set-Cookie" header.
  2. On requests made to the back-end within the same "browser-window-session" (i.e. same tab, no page refreshes, etc), the browser includes the cookie that was set in step 1.
  3. If page is refreshed (or if the application is attempted to be accessed from a separate tab), the browser does not include any cookies in the requests made by the front-end.

More Context:
(with example URLs)

  • The back-end is hosted at https://api.example.com
  • The front-end is hosted at https://ui.example.com
  • The "Set-Cookie" header looks like this: EXAMPLE_SESSION_ID=55A66FFAB27931F115D9E6BA23A11EE4; Max-Age=7200; Expires=Sun, 08-Nov-2020 23:53:39 GMT; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=None
  • CORS Headers:
    • Access-Control-Allow-Origin: https://ui.example.com
    • Access-Control-Allow-Credentials: true

Can anyone help me figure out why the cookie is not being included in requests to the back-end after the page is refreshed?

Edit

Here is the code to configure CORS and Cookies.

Back-End CORS Configuration

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
        corsConfiguration.setAllowedOrigins(getAllowedOrigins());
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedHeaders(
                List.of(
                        "origin",
                        "content-type",
                        "accept",
                        "cookie",
                        "x-csrf-token"
                )
        );
        corsConfiguration.setAllowedMethods(
                List.of(
                        HttpMethod.OPTIONS.name(),
                        HttpMethod.GET.name(),
                        HttpMethod.POST.name(),
                        HttpMethod.PUT.name(),
                        HttpMethod.PATCH.name(),
                        HttpMethod.DELETE.name()
                )
        );

        UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource();
        corsConfigSource.registerCorsConfiguration("/**", corsConfiguration);
        return corsConfigSource;
    }

Back-End Cookie Customization

    @Getter
    @Value("${my.custom.property.session.cookie.domain:}")
    private String customPropertyCookieDomain;

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> stackOverflowExampleWebServerCustomizer() {
        return factory -> {
            factory.addContextCustomizers(customizer -> {
                Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
                cookieProcessor.setSameSiteCookies("None");
                customizer.setCookieProcessor(cookieProcessor);

                if (StringUtils.isNotBlank(customPropertyCookieDomain)) {
                    customizer.setSessionCookieDomain(customPropertyCookieDomain);
                }
            });
        };
    }

Solution

  • After spending hours staring at the "Network" tab in Google Chrome, I decided to see if the cookie I was setting would work if I tried to access the back-end directly through the browser. Sure enough, the cookie was being sent consistently even with page-refreshes and trying in new tabs. I did some digging and it turns out the issue actually had to do with the front-end. The Axios HTTP Client was not setting the withCredentials flag after the page was refreshed. Because my CORS configuration had Access-Control-Allow-Credentials set to true, the preflight (OPTIONS) request-response didn't match the cookie, so the browser stopped appending the cookie to request headers.

    This answer explains the root-cause of the problem with Axios.

    TLDR

    When I refreshed the front-end in the browser, Axios was no longer setting withCredentials=true.