Define CORS origins for multiple environments

I’m trying to configure CORS with the “cors_plug” plugin, but the origins are not the same for 2 envs : development and production.

Here is what I have in config.exs :

config :cors_plug,
  origin: System.get_env("CORS_ORIGINS"),
  headers: ["some_header_name"]

I didn’t know how to store a list (in CI variable maybe) that contains a regex that I will need for the production env (maybe store it as string and then covert it to regex in config file). So to conclude I need this :

For development => origin: [“*”]
For production => origin: [~r/^https?://(?:[a-z0-9_-]+[.])*test.com$/, “https://otherorigin.com”]

Thanks

You could pass a function to origin:

config :cors_plug, origin: &MyCorsHelper.origin/1, headers: ...etc...

Then the function could be different in different environments:

defmodule MyCorsHelper do
  if Mix.env() == :prod do
    @origin_regex ~r/^https?://(?:[a-z0-9_-]+[.])*test.com$/
    def origin(conn) do
      # get config from conn or environment etc
      [@origin_regex, "https://otherorigin.com"]
    end
  else
    def origin(conn) do
      # use conn if needed
      ["*"]
    end
  end
end

Some notes:

  • moving the Mix.env check inside of the origin function may give you a nasty surprise in production; depending on how you’re deploying the whole Mix namespace may not be available at runtime
  • calling System.get_env in a plug’s config is tricky, since the init function that uses that data runs at compile time in production builds

You can use that dodgy method of setting config, :myapp, :env, :prod/:dev in each config file and using Application.compile_env(:myapp, :env) to mitigate concerns of the Mix.env method.

If this is part of your CI you can write file as part of the pipeline with correct value based on Mix.env when doing build. you can create a cors.<env>.exs as sample files and then copy them to your project to cors.exs and have cors.exs imported in your main config. This should be doable in your pipeline keeping configs clean without logic complications

Where can I define the MyCorsHelper module, in the same /config folder ?

And why there is a “&” before the module name and /1 in the end ?

origin: &MyCorsHelper.origin/1

Is this a good solution :

/config/cors_helper.ex

defmodule CorsHelper do
  if Application.compile_env!(:appname, :environment) == "production" do
    @origin_regex ~r/^https?://(?:[a-z0-9_-]+[.])*example.com$/
    def origin(conn) do
      [@origin_regex, "https://otherorigin.com"]
    end
  else
    def origin(conn) do
      ["*"]
    end
  end
end

And in my config.exs OR runtime.exs I will put :

env = System.get_env(“ENV”) => (from CI var)
config :appname,
:environment, env

@cmo @darnahsan @al2o3cr

  • the env settings will be in config.exs OR runtime.exs ?
  • I use Application.compile_env! or Application.fetch_env! to get it ?
  • are the definition of the cors module file and its location are correct ?

The compiler will find files most anywhere in the project, but config is a somewhat odd place to put this. Maybe in the yourappname_web directory in lib, next to router.ex etc?

There’s already MIX_ENV which is used by Phoenix; why add another one? As a general practice, prefer specific environment variables / configs over a broad “env” concept.

Also note that putting that in runtime.exs will not let you configure the value at runtime, it will only offer the choice of:

  • set ENV to the same value at runtime as it was for compilation and work
  • set ENV to something different at runtime and crash
1 Like

It’s unbelievable, the origin config works well but not the headers, here is my final state :

I ended up back to endpoint.ex :

import AppName.ConfigHelpers
...
plug CORSPlug, origin: cors_origins(), headers: cors_headers()

Inside ConfigHelpers :

def cors_origins() do
    if System.get_env("ENV") == "production" do
      [@origin_regex, "https://other.com"]
    else
      ["*"]
    end
  end
def cors_headers() do
    ["test_wrong_header"]
  end

The origins are good, but I have an impression that all headers are allowed, If I deploy with that list [“test_wrong_header”], the request is accepted.