The trick to getting XHR requests to work with Elm or Javascript when developing with Safari

My development of my latest Elm app came crashing to a halt when suddenly pages that had been working fine started returning 401 Unauthorised HTTP errors.

I was writing a Single Page Application (SPA) that was using XHR (Ajax) requests to retrieve information from an existing Rail web site. The SPA was served up from a local test server whilst the API was served from a different host. The behaviour seemed to have no pattern, sometimes the requests went through fine, sometimes the dreaded 401 error.

Inspecting the network requests in the developer console, the problem was obvious – sometimes the requests were sending the session cookie, and sometimes not. I could make the app fail every time by deleting the cookies associated with the API web site. Once I had done this, I couldn’t log in. However, if I opened the API web site in a different tab, and logged in (and even out), my SPA app would now send the session cookie and work fine.

The problem was my privacy settings in Safari. I had chosen Allow from Websites I visit. From the documentation:

Allow from Websites I Visit – Allow all first-party cookies and block all third-party cookies unless that third party was a first party at one time (based on current cookies and browsing history).

The issue came down to the trust being based on my previous browsing activity. Simply visiting the API website in my browser was enough to flag the cookie as a first-party cookie. The SPA then could send the cookie. If that site visit wasn’t in my history (and this seems to be recorded along with the cookie in the cookie store), Safari wouldn’t send the cookie to the third party API website.

So, if you’re developing a Javascript application that uses XHR and testing in Safari, check your Cookie privacy settings, or make a manual visit the API server to enable the cookies to be sent by your application.

Elm, Rails and session cookies

For my first experiments with Elm, I started to write a small app that fetched data from an existing Rails app which uses Cookie sessions for authentication.

I started down the path of looking for how Elm and more specifically the elm-http package handles cookies, and came across a package for Elm for managing cookies, which is unpublished as the authors’ conclusion was that cookies are rarely useful.

I could see the Rails session Set-Cookie header in the response with the information I needed, so I started to look at extracting that information and sending it as a Set-Cookie header on the request. However, the response headers I was seeing in Elm didn’t include it. This is because browser security blocks scripts making Http calls with XMLHttpRequest from accessing cookies – a good thing.

Instead of direct access to cookie data, the handling of cookies is left to the browser. By setting withCredentials to true on the XMLHttpRequest, the browser will send the session cookie sent by Rails app back on future requests. This is especially important for cross-site requests for example when you are developing locally but making API requests from a remote server.

The elm-http package supports setting withCredentials in the settings passed to Http.send.

defaults =
Http.defaultSettings

withCredentials =
{ defaults | withCredentials = True }

task =
Http.send
withCredentials
(corsGet url)

(apologies, the code formatter has lost some of the white spacing)

The final trick is to make sure you set withCredentials to be True on all calls, in particular the call where you authenticate to the server and get the session cookie back for the first time.

Finally, it would seem that this isn’t the best way for Javascript based API calls to authenticate, and JSON Web Tokens (JWT) seem to be a much better approach.