I need to use a Single Page Application (React, Ember, Angular, I don\'t care) with Rails CSRF protection mechanism.
I\'m wondering if I need to create a token evey
I don’t know what exact issue you are facing. But if you are getting CSRF issues in New Rails versions and need to include Rails CSRF tokens in ajax requests you can follow the steps below.
Recently I used Rails 5.1 application.
When using ajax calls to fetch some data from APIs I was getting CSRF token issues:
‘WARNING: Can't verify CSRF token authenticity rails’
The reason was
Rails 5.1 removed support for jquery and jquery_ujs by default, and added
//= require rails-ujs in application.js
It does the following things:
But it is not including the csrf token for ajax request by default. Beware of that. We have to explicitly pass it like:
$( document ).ready(function() {
$.ajaxSetup({
headers: {
'X-CSRF-Token': Rails.csrfToken()
}
});
----
----
});
Note that in Rails 5.1 version, you get ‘Rails’ class in js, and can make use of the functions.
Update: If you are using Rails server side and other front end, you really don't want to use Rails provided CSRF tokens. Because Its really not matter which backend service you are using.
If your aim is to block CSRF, you need to set up CORS in backend, that is your Rails backend. Rails is now providing a separate file in initializer for this. You can mention which sites are allowed to send ajax requests to your backend.
Edit here:
config/initializers/cors.rb
If you want authentication, use basic auth, token auth Or JWT
I'll just answer the question. Full details are explained in this article: https://blog.eq8.eu/article/rails-api-authentication-with-spa-csrf-tokens.html
CSRF tokens are valid during the lifetime of the session. Therefore yes it's ok just to generate one CSRF token for the duration of session after login.
class LoginController < ApplicationController
def create
if user_password_match
# ....
cookie[:my_csrf_token]= form_authenticity_token
end
end
end
or you can refresh the cookie the same way as you are proposing
class ApplicationController < ActionController::Base
after_action :set_csrf_cookie
def set_csrf_cookie
cookies["my_csrf_token"] = form_authenticity_token
end
end
Your SPA just needs to read the cookie and set it as header.
Both are equally valid
or You can just provide the CSRF token as a login response and SPA will store it somewhere and use it in the X-CSRF-Token
header:
curl POST https://api.my-app.com/login.json -d "{"email":'equivalent@eq8.eu", "password":"Hello"}" -H 'ContentType: application/json'
# Cookie with session_id was set
# response:
{ "login": "ok", "csrf": 'yyyyyyyyy" }
# next request
curl POST https://api.my-app.com/transfer_my_money -d "{"to_user_id:":"1234"}" -H "ContentType: application/json" -H "X-CSRF-Token: yyyyyyyyy"
if you are not using cookie to send session_id, therefore your API is using just Authentication
header for authentication. Then you don't need CSRF protection.
No session cookie no CSRF problems !
Example:
curl POST https://api.my-app.com/login.json -d "{"email":'equivalent@eq8.eu", "password":"Hello"}" -H 'ContentType: application/json'
# No cookie/session is set
# response:
{ "login": "ok", "jwt": "xxxxxxxxxxx.xxxxxxxx.xxxxx" }
# Next Request:
curl POST https://api.my-app.com/transfer_my_money -d "{"to_user_id:":"1234"}" -H "ContentType: application/json" -H "Authentication: Bearer xxxxxxxxxxx.xxxxxxxx.xxxxx"
Once again, this is only when you don't use cookies for user identification ! So CSRF is not an issue in this case but you still protect from Cross site scripting attack, make sure your communication is HTTPs only, etc...
If you go with SPA application then you mostly use your Rails only as an API. CSRF token was designed for server rendering... not SPA. In SPA you already use token during authentication, so no need to use another token for CSRF. CSRF was designed as a protection for cross site calls, but API itself designed in a way that it allows request from anywhere until, they are authenticated.
Just disable it for your API and that's all. I would go with some API
namespace and setup a BaseController
, that will be inherited for all API controllers. There you should set protect_from_forgery
:
class API::BaseController < ApplicationController
protect_from_forgery with: :null_session
end
You only need to grab the CSRF token once per session. You can hold onto it in the browser and send it on every (non-GET) request.
Rails will appear to generate a new CSRF token on every request, but it will accept any generated token from that session. In reality, it is just masking a single token using a one-time pad per request, in order to protect against SSL BREACH attack. More details at https://stackoverflow.com/a/49783739/2016618. You don't need to track/store these tokens.
I strongly suggest using Rails's protect_from_forgery
directive rather than encoding the CSRF token in a header yourself. It will generate a different masked token per request.
You can certainly reproduce this yourself with not that much code, but I don't see why you'd need to.
Yes! If you are authenticating with a cookie, you need CSRF protection. This is because cookies are sent with every request, so a malicious website could send a POST request to your site and perform requests on behalf of a logged in user. The CSRF token prevents this, because the malicious site won't know the CSRF token.