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 alsoskip_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.
To set a cookie in REST API response, get the Response reference and use it's cookie() method.
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.
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.
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.
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.
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.
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:
exp
part to avoid JWT replay.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With