0

I built a Chrome extension that saves web content to my Rails app. Originally I was able to rely on the existing Rails/Devise user session to ensure content was being saved to the right user, as long as the CORS settings were opened up on my API controller (see code below). As long as the user was logged in, AJAX calls to my site from the Chrome extension were being authenticated correctly, no matter what site the extension was being used on.

However, in early 2020 Chrome introduced changes to how they they handle cross-site requests (see here, here, and here). Specifically, a cookie's SameSite attribute would now default to 'Lax' instead of 'None', and so to use a cross-site cookie, the cookie setting would need to be explicitly set to SameSite=None; Secure.

Rails' own user session cookie does not have the SameSite=None; Secure settings, and so using the Rails session to authenticate my Chrome extension's request was no longer an option.

My fix was to generate my own API authentication cookie whenever the user logged into the app, which did have the necessary SameSite=None; Secure applied. I was able to authenticate API calls from my Chrome extension using this cookie, and all was well.

And then in early September 2020 it suddenly stopped working. Rails no longer reads the cross-site cookie from Chrome extension requests. There's no error or warning, the value is just null.

API Controller:

  # This gets called when user logs into app:
  def set_cross_site_cookie

    # NOTE: Won't work in dev because secure = true
    cookies[:foo_cookie] = {
      value: 'bar',
      expires: 1.year.from_now,
      same_site: :none, # Required in order to access from Chrome extension on different site
      secure: true # Required in order to access from Chrome extension on different site
    }
    cookie = cookies[:foo_cookie]
    render json: {cookie: cookie}

  end

  # This SHOULD work when called from our Chrome extension:
  def get_cross_site_cookie

    # Add headers to allow CORS requests
    # SEE: http://stackoverflow.com/questions/298745/how-do-i-send-a-cross-domain-post-request-via-javascript
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Request-Method'] = %w{GET POST OPTIONS}.join(",")

    cookie = cookies[:foo_cookie]
    render json: {cookie: cookie}

  end

Rails 5, Rack 2.1 (NOTE: In order to set Rails cookies with option same_site: none you apparently need need to be on a rack version that's higher than 2.1.0 - SEE: https://github.com/rails/rails/pull/28297#issuecomment-600566751)

Anybody know what happened?

Yarin
  • 144,097
  • 139
  • 361
  • 489

1 Answers1

1

I still don't know why the cross-site cookies suddenly stopped working, but here's how I hacked around it:

The workaround was to use the Chrome extension cookie API to read my Rails API authentication cookies into Chrome's local storage. Since we can enable access to cookies of any specific site in the extension manifest, it actually doesn't matter whether they're cross-site cookies or not.

Once the API cookie would get read into storage, we could then pass it along as an auth token with every request, basically using it as a pseudo-cookie.

So the full flow is that the user clicks the extension button, the extension reads in the API authentication cookies based on the explicit cookie permissions it has for that domain, and if the cookie is missing or outdated, it forces the user to log in. If the cookie is valid, it is passed as an auth token in the params or headers of every API call.

SIDENOTE ON PRE-FLIGHT OPTIONS REQUESTS:

You may also have to deal with the OPTIONS pre-flight requests that will be sent with certain cross-site AJAX (I think it's only an issue with content-type JSON POSTS but don't quote me), as they'll trigger an ActionController::RoutingError (No route matches [OPTIONS]) error in Rails. The recommended answer was to use the rack-cors gem, which indeed solved the issue.

SEE:

Yarin
  • 144,097
  • 139
  • 361
  • 489