CORS and basic authentication with cowboy and XmlHttpRequest

So this seems to be anything but straight forward so I document here for the sake of it.

Note that

  • you may not require an Authorization header for the preflight OPTIONS request,
  • you may not set Access-Control-Allow-Origin to the wildcard in that case but to the originating domain specifically,
  • of course Access-Control-Allow-Credentials must be set to true,
  • and last but not least you must advertise the content-type and authorization (for Firefox) header with Access-Control-Allow-Headers (if you want to have some content POST’ed to you),
  • all those headers better be sent with every response.

So in my REST resource I did:

rest_init(Req0, State) ->
    case cowboy_req:header(<<"origin">>, Req0) of
        {undefined, Req1} -> {ok, Req1, State};
        {Origin, Req1}    ->
            %% set CORS headers
            Req2 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>,
                                              Origin, Req1),
            Req3 = cowboy_req:set_resp_header(<<"access-control-allow-methods">>,
                                              <<"POST, OPTIONS">>, Req2),
            Req4 = cowboy_req:set_resp_header(
                     <<"access-control-allow-credentials">>, <<"true">>, Req3),
            Req5 = cowboy_req:set_resp_header(
                     <<"access-control-allow-headers">>, <<"authorization,content-type">>, Req4),
            {ok, Req5, State}
    end.

allowed_methods(Req, State) -> {[<<"OPTIONS">>, <<"POST">>], Req, State}.

is_authorized(Req, State) ->
    case cowboy_req:method(Req) of
        {<<"OPTIONS">>, Req0} -> {true, Req0, State};
        {_Any,          Req0} ->
            case cowboy_req:parse_header(<<"authorization">>, Req0) of
                {ok, {<<"basic">>, {AppId, Token}}, Req1} ->
                    case is_token_valid(AppId, Token) of
                        true ->
                            {true, Req1, AppId};
                        false -> {{false, <<"Basic realm=\"myrealm\"">>}, Req1, State}
                    end;
                {ok, undefined, Req1} ->
                    {{false, <<"Basic realm=\"myrealm\"">>}, Req1, State}
            end
    end.

As for the AJAX request you need to set withCredentials to true (doesn’t work with Firefox, do it manually):

    var r = new XMLHttpRequest();
    r.open("POST", api_uri, true);
    r.setRequestHeader("Authorization", "Basic " + btoa(app_id + ":" + app_token));
    r.setRequestHeader("Content-type", "application/json");
    r.send(JSON.stringify(msg));

I’ve tested with Safari, Chrome and Firefox.

Update: So apparently to make that work with Firefox you have to add authorization explicitly to Access-Control-Allow-Headers. And as for the XmlHttpRequest you have to set the Authorization manually as shown above. Found at http://stackoverflow.com/questions/27001894/firefox-does-not-allow-cors-request-even-when-the-server-returns-the-appropriate

Comments are closed