2

So basically with my app, I keep users access tokens in memory for the duration page load (e.g. once they refresh the token dissapears). To prevent them from having to sign in again, I have my auth server (https://api.myapp.com) set a httpOnly, secure cookie with a refresh token. This works fine for most users, however, some users are able to set custom domains, this means that the client react app is now on theirapp.com and trying to auth with my api on api.myapp.com . This still works in Chrome (latest), Firefox, and Safari Incognito, but does not work in normal safari (seems to completely ignore the refresh cookie).

What possible solutions do I have to making this work? Basically I want it so that:

  1. user navigates to theirapp.com which makes a request to api.myapp.com
  2. This returns a set-cookie: header with a httpOnly; secure; domain; .myapp.com refresh cookie.

I want it so that this cookie is then sent when the user refreshes and the SPA react app makes a POST request to api.myapp.com/auth/refresh. Is this possible in safari (again this works in all other browsers and even safari incognito)?

I don't want to have to resort to storing a longer lived access token in the users localStorage or a non-httpOnly cookie as that's obviously pretty vulernable to XSS.

2
  • is using an iframe an option here?
    – pcalkins
    May 11 '21 at 20:34
  • @pcalkins Note that this question is over a year old and got bumped to home page by the Community script. I doubt the OP is still looking for an answer, but worth answering for other googlers! May 11 '21 at 21:35
1

When cookie A is set by api.myapp.com while the origin of the request is theirapp.com, then cookie A is treated as a third party cookie. Third party cookies were allowed by browsers until recently. Safari announced all third party cookies will be blocked by default. Both Firefox and Google will follow soon.

The suggestion from the said post which likely applies in your case is to use OAuth 2.0 authorisation. Or you could use OpenID connect (which is based on OAuth 2.0).

2
  • Thanks for the help @bhorkarg How exactly does oauth solve this issue? I only ask because I am using oauth2 (google sign in). However, I also struggle to see how would would solve the refresh issue (although I might be missing something about oauth here) as refresh is usually just a network call. Apr 17 '20 at 20:36
  • @HarrisonLucas I am assuming that you do an ajax call from theirapp.com to api.myapp.com to refresh the access token, which is why the cookie is not sent (as it is third party). OAuth2 authorisation code flow should solve this as the authorisation code can be send as a url fragment and then you should exchange the code with access and refresh token. Keep both tokens in memory. If the user refreshes, simply rerun the authorisation code flow (redirect to auth server and get the auth code back to exchange tokens).
    – bhorkarg
    Apr 18 '20 at 9:16
1

I'm not super familiar with Safari, and a quick google did not find any nice Safari-specific documentation, so this answer is generic.

Cookies are a weird choice for APIs. Cookies are for browsers; and are ideally-suited for simple HTML pages (no js) such as simple form POSTs where you just want the browser to do it for you and don't particularly care about security. REST APIs are usually designed with both browser and non-browser clients in mind, which makes cookies -- and particularly things like httpOnly; secure; domain; a really bizarre design choice. I would not be surprised at all if browsers are starting to tighten up on "cross-origin" or "third party" cookies -- which it turns out Safari is doing; see @bhorkarg's answer.

I would suggest moving your user access token out of the cookie and into a non-reserved header like Authorization: bearer which will not have the cross-origin problems you're running into (and has the added bonus as doubling as a CSRF token!)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.