CORS and preflight calls

Trying to set up my 2.6 play application to handle CORS calls. I’ve added the CORSModule to my application.conf and left all the play.filters.cors settings as their defaults. Is there something additional I need to do in order to allow preflight calls into my application? I keep getting a 401 for the OPTIONS call into my API.

Here’s how I did it back when we needed to do it in code.

import javax.inject.Inject

import play.api.http.HttpFilters
import play.filters.cors.CORSFilter
import play.filters.gzip.GzipFilter

class Filters @Inject() (corsFilter: CORSFilter, gzipFilter: GzipFilter) extends HttpFilters {
  def filters = Seq(corsFilter, gzipFilter)
}

The new docs suggest adding it to your configuration should be fine.

As the docs suggest, you should probably enable the filter like this. You don’t need to add the module.

play.filters.enabled += "play.filters.cors.CORSFilter"

Starting in dev mode, the following is printed to the console:

Showing what filters is enabled.

Andy’s suggestion will work too though.

1 Like

Still getting a 404 (not a 401 as I originally reported) after enabling the CORSFilter. I’m not getting a listing of the enabled filters when I start up in dev mode (nor in prod mode.) It looks like my logback.xml file is setting the level for “play” to “INFO” and I have gotten a few (very few) info lines written out on startup, but nothing about filters. Any idea how to see whether my filter is actually being used or how I might debug this further?

OK, turns out the REST API template that play provides uses the
play.http.filters = play.api.http.NoHttpFilters
setting. And it didn’t necessarily occur to me that play.http.filters would disable all play.filters as the namespaces are different.

I still am not getting logging of the enabled filters, but I am getting proper handling of OPTIONS preflight calls.

1 Like

It’s be great if you could issue a PR in the docs to reflect that behavior.

Are you asking that of me? What is a PR if so?

@PeeDub, a PR is a pull request.

Whenever you want to improve something on a GItHub project you first make a fork of the project, make your changes and push them to your fork. Once you have the changes on your fork, you invite the maintenairs of the project to have a look and eventually integrate it. To do so, you send a pull request to them. You ask them to pull the changes from your fork into the main project repository. You can find more about PRs here:
https://help.github.com/articles/creating-a-pull-request-from-a-fork/.

And yes, @andyczerwonka is suggesting you to improve the docs by sending a PR with the findings you did. :slight_smile:

Alternatively, you can open an issue on the GitHub repo and describe what you have described here. That will help the maintainers to track it down.

3 Likes

Hi Renato,
I’ve followed the documentation to the letter, looked on gitter and stackoverflow but non of the suggestions see to work. I’m on Play 2.8.1 Scala, and I just can’t get CORS to work.
What I’m I missing?

play.filters {

enabled = [
  "play.filters.headers.SecurityHeadersFilter",
  "play.filters.hosts.AllowedHostsFilter",
  "play.filters.gzip.GzipFilter",
  "play.filters.cors.CORSFilter"
]

hosts {
    allowed = ["."]
}

cors {
    pathPrefixes = ["/"]
    allowedOrigins = null
    allowedHttpMethods = null
    allowedHeaders = null
    #exposedHeaders = ["Access-Control-Allow-Origin", "content-type"]
    exposed.headers += "Access-Control-Allow-Origin"
    serveForbiddenOrigins = true
}

}

In my case, a multitenant app with different CORS headers per tenant, I had to generate CORS headers myself (Play’s filter wasn’t flexible enough), maybe that code can be helpful to you. There’s not really any need to use Play’s filters — you can just add the CORS headers yourself.

def checkIfCorsRequest(), https://github.com/debiki/talkyard/blob/236226766862c1a92b127d3cf6006197784967fb/app/ed/server/security/package.scala#L167

corsInfo match { case CorsInfo.OkayCrossOrigin(okayOrigin, _, _) ... https://github.com/debiki/talkyard/blob/f5313d24a50866590dbc19ae6ad752a0b8740700/app/ed/server/http/PlainApiActions.scala#L175

Thanks! I ended up going back to using an extension class on Result.

object ResultExtensions {

  implicit class CorsResult(result: Result) {
    def withCors = result.withHeaders(
      "Access-Control-Allow-Origin" -> "*"
      , "Access-Control-Allow-Methods" -> "OPTIONS, GET" // OPTIONS for pre-flight
      , "Access-Control-Allow-Headers" -> "Accept, Content-Type, Origin, X-Json, X-Prototype-Version, X-Requested-With"
      , "Access-Control-Allow-Credentials" -> "true"
    )
  }

}
UnprocessableEntity(Json.obj("errors" -> errors)).withCors