CORS Settings in Phoenix

Every web developer has come across a CORS error before. It's one of the last things you want to deal with when putting together your application. You just want to get the code moving and worry about this later.

Fortunately, Phoenix makes it easy to build and serve frontend code from the same origin, usually making CORS a non-issue. This is not always an acceptable setup. Sometimes you want to run the client at a separate origin, the client is actually anyone consuming your public api, or you need to allow all cross origin requests temporarily. In this post, I'll layout how you can manage CORS settings in your Phoenix app so it's not a problem.

I've setup an example project where each commit roughly follows the steps that follow. If you want to go ahead and take a look, here you go.

For our example, we have an application with a Phoenix backend and a React frontend. It's a fairly small app that displays a list of stored images. The React app is setup to be served separately from the Phoenix application. This means the default create-react-app and Phoenix configurations will result in CORS errors in development. More specifically, the browser will not allow a client application at origin A to read content from a server at origin B without the presence of a CORS header. In order to add CORS headers, we can use a library called Corsica. Let's add it as a dependency for your Phoenix app. The deps declaration in your mix.exs file should look something like this:

defp deps do
    [
      ...
      {:corsica, "~> 1.1.3"}
    ]
end

With Corsica declared as a dependency, all we have to do is make a single configuration change. Add the following to your endpoints.ex file.

plug Corsica,
    origins: "*"

If you reload the react app, then you should see the images stored by your Phoenix app. There is a problem with this approach though. Adding the wildcard CORS header means that any address can make requests to our server. This potentially leaves it more vulnerable than we would like. An alternative approach is to use a whitelist of valid domains. We could use the following configuration to limit the domains to just our React app running locally.

plug Corsica,
    origins: ["http://localhost:3000"]

At the very least this restricts access to the only client that is relevant for our current purposes. But what about production? We don't want to change our configuration manually just to develop locally or for our server in production. This is where configs come into play. We can offload the value of the origins list to the prod or dev config.

The endpoint.ex Corsica configuration will have to be updated to retrieve the configuration. Application.get_env will do the trick. The first argument should be the application under which the config property has been stored. If you look at your mix.exs file, you will find the available applications in the application function. The second argument should be the name of the config property being accessed. In our case that property is :origins. The Corsica config should look something like this.

plug Corsica,
    origins: Application.get_env(:your_app_id, :origins)

We will have to store our config values in the files appropriate for their environment, dev and prod. This can be done with the config function as follows.

config/prod.exs:

config :your_app_id, origins: ["YOUR_PROD_APP_URL"]

config/dev.exs:

config :your_app_id, origins: ["http://localhost:3000"]

Now, you should have a fully functioning origin whitelist for CORS requests. Corsica is capable of much more than origin whitelists. I won't go into detail here, but I've also used it for content-type and authorization headers. For more information, check out the Corsica documentation.

Subscribe for my latest posts and weekly news review