2

I am building a simple Spring Server project using the GraphQL Kotlin library created & open-sourced by Expedia. I have a backend up and running, talking to the datastore, and I'm able to fetch data by sending queries via Playground.

When I try to connect from my React frontend (using Apollo), the initial OPTIONS request gets a 404 response due to a CORS issue. I am relatively new to Spring Boot and there are a lot of potential approaches documented on SO and elsewhere.

How do I intercept the response & add the appropriate Access-Control-Allow-Origin header?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Tim Keating
  • 6,443
  • 4
  • 47
  • 53

4 Answers4

2

After trying many of the aforementioned approaches, I found one that worked for my particular combination of variables.

These, for example, did not work:

  1. WebFluxConfigurer component that overrides the addCorsMappings function
  2. @CrossOrigin annotation on the query function
  3. Add the spring boot actuator lib to the project and add management.endpoints.web.cors config to the application.yml, per the Spring documentation

What ultimately did work for me was a custom WebFilter subclass, per this SO question: Enable CORS in Spring 5 Webflux?

and also this tutorial, which illuminated a lot about how Spring Boot works in general.

Tim Keating
  • 6,443
  • 4
  • 47
  • 53
2

You can do like this:

 @Bean
    @Profile("!production")
    fun corsFilter(): CorsWebFilter = CorsWebFilter(
        UrlBasedCorsConfigurationSource().apply {
            registerCorsConfiguration(
                "/**",
                CorsConfiguration().apply {
                    allowCredentials = true
                    allowedOrigins = listOf("*")
                    allowedHeaders = listOf("*")
                    allowedMethods = listOf("*")
                }
            )
        }
    )
Dmitry Kaltovich
  • 2,046
  • 19
  • 21
1

For me (using same technologies like you), creating custom filter component (CorsFilter) worked the best out of all other options:

Example of my configuration:

@Component
class CorsFilter: WebFilter {

    @Value("\${cors.allowed_origin}")
    lateinit var allowedOrigin: String

    override fun filter(ctx: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        ctx.response.headers.add("Access-Control-Allow-Origin", allowedOrigin)
        ctx.response.headers.add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS")
        ctx.response.headers.add("Access-Control-Allow-Credentials", "true")
        ctx.response.headers.add("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range, Authorization")
        return when {
            ctx.request.method == HttpMethod.OPTIONS -> {
                ctx.response.headers.add("Access-Control-Max-Age", "1728000")
                ctx.response.statusCode = HttpStatus.NO_CONTENT
                Mono.empty()
            }
            else -> {
                ctx.response.headers.add("Access-Control-Expose-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range")
                chain.filter(ctx)
            }
        }
    }
}

note: allowedOrigin variable is taken from applications.properties files

0

Only adding the next configuration in spring boot-kotlin server work with React and Apollo:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import org.springframework.web.cors.reactive.CorsUtils
import org.springframework.web.server.WebFilter
import reactor.core.publisher.Mono

@Configuration
class ReactiveConfigurations {

    companion object {
        const val ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN"
        const val ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS"
        const val ALLOWED_ORIGIN = "*"
        const val MAX_AGE = "3600"
    }

    @Bean
    fun corsFilter(): WebFilter {
        return WebFilter { ctx, chain ->
            val request = ctx.request
            if (CorsUtils.isCorsRequest(request)) {
                val response = ctx.response
                val headers = response.headers
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN)
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS)
                headers.add("Access-Control-Max-Age", MAX_AGE)
                headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS)
                if (request.method === HttpMethod.OPTIONS) {
                    response.statusCode = HttpStatus.OK
                    return@WebFilter Mono.empty<Void>()
                }
            }
            chain.filter(ctx)
        }
    }
}