How to properly implement CORS in plug_cowboy served REST-API?

Hi Elixir Community,

after trying out both CORS libraries on the market (namely corsica and cors_plug) without success I am trying to implement CORS conformity myself, according to this SO answer.

It works fine for simple GET, POST requests (as it did with cors_plug) but it does not work with PATCH requests and I don’t know why.
Here’s the code in question:


# Wlanbot.Router does forward to this router
defmodule Wlanbot.Router.SessionRouter do
  import Plug.Conn
  use Plug.Router
  require Logger
  require Ecto.Query
  alias Wlanbot.{Session, Room, Repo, Session}

  plug(:match)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Jason)
  plug(:dispatch)

  defdelegate json(conn, map), to: Wlanbot.Router
  defdelegate to_num(str), to: Wlanbot.Router
  defdelegate keys_to_atoms(map), to: Wlanbot.Router
  defdelegate get_active_session(), to: Wlanbot.Router

  options "/active" do
    conn
    |> put_resp_header("Access-Control-Allow-Origin", "*")
    |> put_resp_header("Access-Control-Allow-Method", "POST, GET, PATCH, OPTIONS")
    |> put_resp_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
    |> send_resp(200, "")
  end

  patch "/active" do
    end_date = try do
      conn.params["end_date"] 
      |> NaiveDateTime.from_iso8601!()
      |> NaiveDateTime.truncate(:second)
    rescue 
      e -> Logger.error("Got error #{e.message}") && nil
    end

    active_session = Ecto.Changeset.change(get_active_session(), end_date: end_date)
    case Repo.update active_session do
      {:ok, struct}       -> json(conn |> put_resp_header("Access-Control-Allow-Origin", "http://localhost:3000"), struct) 
      {:error, changeset} -> send_resp(conn, 500, inspect(changeset.errors)) 
    end
  end

Why does this not work and instead results in the error (from frontend console):

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/rooms/grouped/page/0. (Reason: CORS request did not succeed)

I’m not sure how that path gets to this router, but note that it’s Access-Control-Allow-Methods (plural) in the CORS spec.

1 Like

Thanks, I just fixed that typo but I still get the same error message.

I’m not sure how that path gets to this router

defmodule Wlanbot.Router do
  import Plug.Conn
  use Plug.Router
  require Logger
  require Ecto.Query
  alias Wlanbot.{Repo, Session}

  # Using Plug.Logger for logging request information
  plug(Plug.Logger)
  # responsible for matching routes
  plug(:match)
  # Note, order of plugs is important, by placing this _after_ the 'match' plug,
  # we will only parse the request AFTER there is a route match.
  plug(Plug.Parsers, parsers: [:json], json_decoder: Jason)
  # responsible for dispatching responses
  plug(:dispatch)

  Plug.Router.forward("/sessions", to: Wlanbot.Router.SessionRouter)
  Plug.Router.forward("/rooms", to: Wlanbot.Router.RoomRouter)
  Plug.Router.forward("/wifi_data", to: Wlanbot.Router.WifiDataRouter)

  match(_, do: conn |> send_resp(404, "Not found"))

end

Clarifying my question from before:

The posted CORS header code was written in SessionRouter, but the error message:

would dispatch to RoomRouter. Is there similar CORS-setting code in that module?

Oops, my fault - I posted the CORS error message from another module, but it is interchangeable.
Your guess is correct, however the posted error for the RoomRouter was fixed by implementing an OPTIONS route for it (contrary to the SessionRouter where the OPTIONS route doesnt fix it).

@al2o3cr Thank you for your help, but the failure’s root cause was laying in the frontend. The backend routes were fine!