Skip to content

Make Cowboy CORS friendly #947

Description

@manifest

Maybe we could make Cowboy a bit more CORS friendly?
By now, to enable CORS we should work with raw headers, copy-pasting code like this from one header to another:

options(Req, State) ->
  Req1 = cowboy_req:set_resp_header(<<"access-control-max-age">>, <<"1728000">>, Req0),
  Req2 = cowboy_req:set_resp_header(<<"access-control-allow-methods">>, <<"HEAD, GET, POST">>, Req1),
  Req3 = cowboy_req:set_resp_header(<<"access-control-allow-headers">>, <<"content-type, authorization">>, Req2),
  Req4 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>, <<$*>>, Req3),
  {ok, Req, State}.

In this way we do not actually care about allowed header's value types. For instance, "access-control-allow-methods" and "access-control-allow-headers" should only accept a list of binaries, while only a numeric value makes sense for the "access-control-max-age" header, "*" origin cannot be used for a resource that supports credentials. Ignoring this information leads to misspelling errors, breaking CORS responses and isn't flexible for working with.

My proposal is to add two new functions (set_cors_headers/2 and set_cors_preflight_headers/2) to the cowboy_req module extending functionality of set_resp_header/3.

-type cors_header()
  :: {origin, binary() | list(binary())}.

-type cors_preflight_header()
  :: {age, max | non_neg_integer()}
   | {methods, list(binary())}
   | {headers, list(binary())}
   | cors_header().

-spec set_cors_headers(list(cors_header()), Req) -> Req when Req :: cowboy_req:req().
-spec set_cors_preflight_headers(list(cors_preflight_header()), Req) -> Req when Req :: cowboy_req:req().

In my projects, I'm using this simple syntax sugar in REST handlers:

allowed_methods(Req, State) ->
  Req2 = cowboy_req:set_cors_headers([{origin, <<$*>>}], Req),
  {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>], Req2, State}.

options(Req, State) ->
  Headers =
    [ {age, max},
      {origin, <<$*>>},
      {methods, [<<"HEAD">>, <<"GET">>, <<"POST">>]},
      {headers, [<<"content-type">>, <<"authorization">>]} ],
  {ok, cowboy_req:set_cors_preflight_headers(Headers, Req), State}.

It's also possible to use those functions in a middleware:

execute(Req, Env) ->
  case cowboy_req:method(Req) of
    <<"OPTIONS">> ->
      Headers =
        [ {age, max},
          {origin, <<$*>>},
          {methods, [<<"HEAD">>, <<"GET">>, <<"POST">>]},
          {headers, [<<"content-type">>, <<"authorization">>]} ],
      {ok, cowboy_req:set_cors_preflight_headers(Headers, Req), Env};
    _ ->
      {ok, cowboy_req:set_cors_headers([{origin, <<$*>>}], Req), Env}
  end.

I believe those functions could make Cowboy yet more friendly and useful tool and would love to make a pull request, if it's ok with Cowboy's philosophy.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions