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.
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.
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
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