Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: How to implement protect_from_forgery in Rails API mode

I have a Rails 5 API app (ApplicationController < ActionController::API). The need came up to add a simple GUI form for one endpoint of this API.

Initially, I was getting ActionView::Template::Error undefined method protect_against_forgery? when I tried to render the form. I added include ActionController::RequestForgeryProtection and protect_from_forgery with:exception to that endpoint. Which solved that issue as expected.

However, when I try to submit this form I get: 422 Unprocessable Entity ActionController::InvalidAuthenticityToken. I've added <%= csrf_meta_tags %> and verified that meta: csrf-param and meta: csrf-token are present in my headers, and that authenticity_token is present in my form. (The tokens themselves are different from each other.)

I've tried, protect_from_forgery prepend: true, with:exception, no effect. I can "fix" this issue by commenting out: protect_from_forgery with:exception. But my understanding is that that is turning off CSRF protection on my form. (I want CSRF protection.)

What am I missing?

UPDATE:

To try to make this clear, 99% of this app is a pure JSON RESTful API. The need came up to add one HTML view and form to this app. So for one Controller I want to enable full CSRF protection. The rest of the app doesn't need CSRF and can remain unchanged.

UPDATE 2:

I just compared the page source of this app's HTML form and Header with another conventional Rails 5 app I wrote. The authenticity_token in the Header and the authenticity_token in the form are the same. In the API app I'm having the problem with, they're different. Maybe that's something?

UPDATE 3:

Ok, I don't the the mismatch is the issue. However, in further comparisons between the working and non-working apps I noticed that there's nothing in Network > Cookies. I see a bunch of things like _my_app-session in the cookies of the working app.

like image 402
lostphilosopher Avatar asked Mar 14 '17 19:03

lostphilosopher


2 Answers

Here's what the issue was: Rails 5, when in API mode, logically doesn't include the Cookie middleware. Without it, there's no Session key stored in a Cookie to be used when validating the token I passed with my form.

Somewhat confusingly, changing things in config/initializers/session_store.rb had no effect.

I eventually found the answer to that problem here: Adding cookie session store back to Rails API app, which led me here: https://github.com/rails/rails/pull/28009/files which mentioned exactly the lines I needed to add to application.rb to get working Cookies back:

config.session_store :cookie_store, key: "_YOUR_APP_session_#{Rails.env}" config.middleware.use ActionDispatch::Cookies # Required for all session management config.middleware.use ActionDispatch::Session::CookieStore, config.session_options 

Those three lines coupled with:

class FooController < ApplicationController   include ActionController::RequestForgeryProtection   protect_from_forgery with: :exception, unless: -> { request.format.json? }   ... 

And of course a form generated through the proper helpers:

form_tag(FOO_CREATE_path, method: :post)   ... 

Got me a CSRF protected form in the middle of my Rails API app.

like image 124
lostphilosopher Avatar answered Sep 22 '22 12:09

lostphilosopher


If you're using Rails 5 API mode, you do not use protect_from_forgery or include <%= csrf_meta_tags %> in any view since your API is 'stateless'. If you were going to use full Rails (not API mode) while ALSO using it as a REST API for other apps/clients, then you could do something like this:

protect_from_forgery unless: -> { request.format.json? } 

So that protect_from_forgery would be called when appropriate. But I see ActionController::API in your code so it appears you're using API mode in which case you'd remove the method from your application controller altogether

like image 20
Ricky Brown Avatar answered Sep 18 '22 12:09

Ricky Brown