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
I had this challenge when working on a Rails 6 API only application.
Here's how I solved it:
First, include this in your app/controllers/application_controller.rb file:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
end
Note: This was added because protect_from_forgery is a class method included in ActionController::RequestForgeryProtection which is not available when working with Rails in API mode.
Next, add the cross-site request forgery protection:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
protect_from_forgery with: :null_session
end
OR this if you want to protect_from_forgery conditionally based on the request format:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
protect_from_forgery with: :exception if proc { |c| c.request.format != 'application/json' }
protect_from_forgery with: :null_session if proc { |c| c.request.format == 'application/json' }
end
Finally, add the line below to your config/application.rb file. Add it inside the class Application < Rails::Application class, just at the bottom:
config.middleware.use ActionDispatch::Flash
So it will look like this:
module MyApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.1
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
config.middleware.use ActionDispatch::Flash
end
end
Note: This will prevent the error below:
NoMethodError (undefined method `flash=' for #<ActionDispatch::Request:0x0000558a06b619e0>):
That's all.
I hope this helps
No need of protect_from_forgery for AJAX calls and apis.
If you want to disable it for some action then
protect_from_forgery except: ['action_name']
class Api::ApiController < ApplicationController
skip_before_action :verify_authenticity_token
end
Use as above with rails 5
Related
The title is my question.
devise provide us many useful methods like current_user, authenticate_user!, and so on. I want to know why is it possible to use them without including any module like below.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
Those method's definition is here
Somebody please help me!
The answer is devise included its helper methods into ActionController on behalf of you when Rails on_load
# devise/rails.rb
module Devise
class Engine < ::Rails::Engine
# ...
initializer "devise.url_helpers" do
Devise.include_helpers(Devise::Controllers)
end
# ...
end
# devise.rb
# ...
def self.include_helpers(scope)
ActiveSupport.on_load(:action_controller) do
include scope::Helpers if defined?(scope::Helpers)
include scope::UrlHelpers
end
ActiveSupport.on_load(:action_view) do
include scope::UrlHelpers
end
end
# ...
I saw many 3rd gems using on_load to include their methods (or themselves) into Rails core, maybe it's a typical way to do that (allows Rails to lazily load a lot of components and thus making the app boot faster). If you install some gems and you could use their methods on your model/controller/view then those gems did the same thing devise did above.
About those methods current_user, authenticate_user! ... they are dynamic methods devise will generate when it did include scope::Helpers into Rails (see code above and the link).
So one very good thing about rails is the fact that you get a lot of things for free out of the box. One of these things at the top level is autoloading.
So in the case of Devise. When you install Devise and run the generate command, you get a devise.rb file inside of your config/initializers/ folder. This file is always autoloaded by rails on server startup and reload. That's how all these methods are able to be use these devise methods without importing anything.
Read more at rails docs
I try to employ the public_suffix gem to set content of a site based on the top level domain but always get the error uninitialized constant ApplicationController::PublicSuffix.
The code of application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
helper :all
before_filter :set_tld
def set_tld
servername = request.env['SERVER_NAME']
tld = PublicSuffix.parse(servername).tld
end
end
I already tried to do a require 'public_suffix' inside the function as well as include PublicSuffix after protect_from_forgery but to no avail.
After more than a couple of server restarts my code suddenly works - I don't know how but I guess sometimes miracles happen.
Case closed.
Got this error while installing active admin on rails 4.0.2 application. I am using rails-api, where I need something like this admin to manage content apart from client.
undefined method `layout' for ActiveAdmin::Devise::SessionsController:Class
I am not sure about this error.
Okay looks like it has been solved by adding this on application controller.
include AbstractController::Layouts
It is now ActionView::Layouts
See https://github.com/rails/rails/issues/14517
When use rails-api(it's merged into rails 5 now), your ApplicationController will inherits from ActionController::API instead of ActionController::Base, while rails admin dependents on ActionController::Base and some other middlewares. To make active admin works with rails api mode, your need to do some additional work:
Make your ApplicationController inherit from ActionController::Base
class ApplicationController < ActionController::Base
Modify your config/application.rb like this
class Application < Rails::Application
# ...
config.middleware.use ActionDispatch::Flash
config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Cookies
end
References:
1. https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration.html
2. What is the difference between a regular Rails app and a Rails API?
Is there a way to disable all cookies for a Rails app? Or preferably on a controller by controller basis? My problem is regarding access of a Rails JSON api by an Adobe Lightroom plugin. Apparently the presence of any cookie data in the response from the server causes an error in Lightroom.
In the controller you want to avoid cookies, add this:
after_filter :skip_set_cookies_header
def skip_set_cookies_header
request.session_options = {}
end
If you have a set of api controllers, set this in a api_controller class and let your other controllers inherit the api_controller.
This skips setting Set-Cookie header since the session opts is empty.
If you're using Apache, you can turn probably turn off cookies in the response by using mod_headers, which is a standard apache mod.
Header always unset "Set-Cookie"
You might want to use ActionController::Metal and add any additional modules that you might need.
ActionController::Metal is pretty barebone and skips most of the functionality of a typical ApplicationController including cookies.
You can call ApplicationController.ancestors to get an idea of what is typically included in contrast with ActionController::Metal.ancestors
Here's how I would most likely set it up.
class SimpleController < ActionController::Metal
#include ...
#include ...
end
class FirstApiController < SimpleController
def index
#Code
end
end
class SecondApiController < SimpleController
def index
#Code
end
end
Our application is developed using Rails 2.3.5 along with ActiveScaffold. ActiveScaffold adds quite a bit of magic at run time just by declaring as following in a controller:
class SomeController < ApplicationController
active_scaffold :model
end
Just by adding that one line in the controller, all the restful actions and their corresponding views are made available due to ActiveScaffold's meta programming. As most of the code is added at runtime, in development mode requests seems to be little slower as there is no class_caching.
We needed to add a authorization layer and my team has chosen Lockdown plugin which parses an init.rb file where you declare all the authorization rules. The way that Lockdown stores the authorization rules is by parsing the init.rb file and evaluating the controllers declared in the init.rb file. So for every request Lockdown evaluates all the controllers thereby forcing ActiveScaffold to add lot of meta programming which in turn makes db queries to find out the column definitions of every model. This is considerably slowing down the request in development as there is no class_caching. Some times are requesting are taking almost 30-45 seconds.
Is there any way to force ActiveScaffold to do its magic in a before_filter? Something like the following:
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.instance_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.instance_eval do
active_scaffold :model
end
end
end
I tried all the above four options, when I make a request, browser seem to show the loading indicator but nothing is happening.
Any help is appreciated.
Thanks in advance.
Lockdown only reparses init.rb in development mode so you can make changes without restarting the application. It will be slower - a convenience trade off. Good news is that Lockdown will only do this parsing once in production mode.
I don't use ActiveScaffold, so I can't offer any help there, but thought this would be of interest to you.