Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Set-Cookie on json API response

Simple token based auth for Rails.
Client server makes a request to the API server. The API server returns success status, json-encoded body and ideally sets a cookie.

# response from API  
render json: {user: "user", token: "token_string"}, status: :created

How can this response also set a cookie?
I've tried to append , set_cookie: token to the render statement. I've also
skip_before_filter :verify_authenticity_token, only: :create

The end goal is to store the token in a browser cookie. I do not want to use HTML5 storage for the token.

like image 801
csi Avatar asked Sep 02 '15 13:09

csi


People also ask

Can API response set cookie?

To set a cookie in REST API response, get the Response reference and use it's cookie() method.

How do I set cookies in API?

set() The set() method of the cookies API sets a cookie containing the specified cookie data. This method is equivalent to issuing an HTTP Set-Cookie header during a request to a given URL. The call succeeds only if you include the "cookies" API permission in your manifest.

What are cookies rails?

Cookies, Sessions and Flashes are three special objects that Rails gives you in which each behave a lot like hashes. They are used to persist data between requests, whether until just the next request, until the browser is closed, or until a specified expiration has been reached.


1 Answers

This is my setup for a cross-domain SPA/API setup:

I have this in my controller, using the Knock gem, action:

class AccountTokenController < Knock::AuthTokenController
  def  create
    response.set_cookie(
      :jwt,
      {
        value: auth_token.token,
        expires: 30.minutes.from_now,
        path: '/api',
        secure: Rails.env.production?,
        httponly: Rails.env.production?
      }
    )

    render json: auth_token.payload, status: :created
  end
end

That will set a cookie on the client as well as returning all the token data in the response body, including a CSRF token.

You also need to make sure that your AJAX requests include the cookie, which they don't by default. With Axios, you do this by setting the option withCredentials: true. For other libraries, it will be something similar.

CORS

If you have CORS configured on the API server, and you should, you need some additional config there as well. I use the Rack::CORS gem for this and my config looks like this (config/initializers/cors.rb):

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

Note the config for credentials: true as well as the need to specify a specific host under origins, neither * nor a list of domains will work in this case.

CSRF and XSS

Very basically Cross-Site Request Forgery is when another domain makes a request to your backend on the assumption that your user is logged in and has a session cookie. So it is based on cookies and come from a third party domain and as such can not access anything in your browser.

XSS, Cross-Site Scripting is when someone manages to run a script on YOUR page, and hence your domain. This sort of attack can access anything in your browser, for that domain. This includes sessionStorage and localStorage as well as normal cookies that the browser can read.

Why this works

The CSRF token is present both in the session cookie and sent along with each API request as a header, giving protection from both CORS and XSS while keeping the backend stateless.

All any backend service has to do is:

  1. Verify the JWT signature (to ensure it's not a faked or manipulated session cookie being returned).
  2. Verify the exp part to avoid JWT replay.
  3. Compare the header CSRF token to the one in the JWT.
  4. Verify the scopes in the JWT (this will be application specific).

The full JWT is signed with the servers private key before being used as a cookie and as such cannot be manipulated without detection.

So any CSRF attack will fail since it only has indirect access to the session cookie and won't be able to read the CSRF token from it, so any request will fail.

Any XSS attack will fail because even though they can read the CSRF token in localStorage they can not get the session cookie. The only way to do that would be to run requests from your browser session against the backend, and while that could be possible that is not generally how these attacks work. They normally try to export the session info so that they can make requests from another server, and that won't work here.

like image 171
Jonas Schubert Erlandsson Avatar answered Oct 02 '22 13:10

Jonas Schubert Erlandsson