Search code examples
ruby-on-railscookiesxmlhttprequestrails-api

Sending cookie in XHR response but it's not getting stored?


I'm trying to pass an HttpOnly cookie with a response from an API I'm writing. The purpose is for the cookie act like a refresh token for the purpose of silent refresh for a SPA in React.

In my controller method, I've got the following code:

response.set_cookie(
  :foo,
  {
    value: 'testing',
    expires: 7.days.from_now,
    path: '/api/v1/auth',
    secure: true,
    httponly: true
  }
)

I'm making a post request to this action with a fetch command like so:

fetch("http://localhost:3001/api/v1/auth", {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    email: '[email protected]',
    password: '123456',
  })
})

Not sure if that matters but I'm wondering if passing cookies in a XHR response doesn't work? However, this seems to be working as you can see in my response I'm getting this:

Set-Cookie: foo=testing; path=/api/v1/auth; expires=Sun, 26 Jan 2020 05:15:30 GMT; secure; HttpOnly

Also in the Network tab under Cookies I'm getting this: Network -> Cookies tab

However, I'm NOT getting the cookie set under Application -> Cookies: Cookies not showing up

To clarify, the React app is sitting on localhost:3000 and the rails backend is listening on localhost:3001.

Any ideas?


Solution

  • Ok, so it looks like I needed to configure my CORS (in Rails this is your Rack::CORS middleware.

    I setup my config/initializers/cors.rb file like so:

    Rails.application.config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins 'http://localhost:3000'
        resource '*',
          headers: :any,
          methods: [:get, :post, :put, :patch, :delete, :options, :head],
          credentials: true
      end
    end
    

    and my fetch command should look something like this with credentials: 'include' as a parameter:

    return fetch(`${endPoint}`, {
        method: 'POST',
        credentials: 'include',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          email,
          password,
          password_confirmation: passwordConfirmation
        })
      })
    

    Adding credentials: true allows cookies to be set by the browser. Apparently, even if you send them, you need Access-Control-Allow-Credentials: true in the headers for the browser to do anything with them.

    EDIT: Recreating this application for learning experience I came across this issue again even after including the credentials option. I wasn't seeing the HttpOnly cookie being stored in the browser. Turns out however, that it WAS and does get sent. You can probably test for this in a controller action. Keep this in mind if this solution doesn't 'seem' to work for you!