simple_token_authentication
simple_token_authentication copied to clipboard
Q: What is the simple_token_authentication configuration for an api?
When using this gem for a json api exclusively what is the best practice configuration? How are controllers protected since there is no "authenticate_user" method.
My config is below, please correct me.
for this gem:
-
config.sign_in_token = false
-
acts_as_token_authentication_handler_for User, fallback_to_devise: false
In application_controller.rb, I assume this is diabling json csrf protection? 3) protect_from_forgery with: :null_session -> csrf protection 4) skip_before_action :verify_authenticity_token, if: :json_request?
In devise gem configs to disable redirects: 5) config.to_prepare do DeviseController.respond_to :json end 6) config.navigational_formats = ['/', :html]
My issues:
With the above configuration in an api should I be disabling csrf and devise fall_back? #49 seems to imply that the devise fallback should be disabled for api is this correct?
Also, since the fallback is disabled will I have to handle cases in which current_user is missing?
Hi @Rotimi,
The answer is yes to both questions:
- As you correctly noticed, the CSRF protection should be disabled for an API (see the issue 37 for references). Now, once the CSRF protection is disabled, it's very important to disable this gem fallback to Devise (as explained in #49). That's the whole point of the
fallback_to_devise: false
option (documented in the Installation section of theREADME
). - You're also correct about the second part: since there is no fallback if token authentication fails, you must restrict access to your data when
current_user
is missing. You could also use therequire_authentication_of_entity!
method (a good option IMO) written by @donbobka in this commit (take a look at #61 for context). The current release doesn't include it, and you would have to add it by yourself, but you could of course find help here if necessary.
@Rotimi, IMO the best way of gem usage for api following:
-
protect_from_forgery with: :null_session
- It isn't a disabling of CSRF protection, it's another method of protection:
:null_session
- Provides an empty session during every request -
config/initializers/simple_token_authentication.rb
without modification - Example:
# /app/controller/api/base_controller.rb
class API::BaseController < ActionController::Base
protect_from_forgery with: :null_session # CSRF protection for API
end
# /app/controller/api/v1/post_controller.rb
class API::V1::PostController < API::BaseController
acts_as_token_authentication_handler_for User, only: [:new]
def index
# ... Accessible for unauthenticated users ...
end
def show
# ... Accessible for unauthenticated users ...
end
def new
# ... Only for authenticated users...
# If current_user is missing, devise throw error with status code 401
end
end
In this way you have a CSRF protection and devise will handle cases when current_user is missing
Note @Rotimi: @donbobka is right, protect_from_forgery with: :null_session
doesn't disable CSRF protection, that's why the skip_before_action :verify_authenticity_token, if: :json_request
instruction is necessary (Rails request forgery protection documentation).
For reference: #45
IMO this documentation(Rails request forgery protection documentation) is obsolete. Because without verify_authenticity_token
call protect_from_forgery with: :null_session
do nothing.
I may be wrong, but I think that's the point. AFAIK the CSRF protection does not make sense for API (I have previously references this post about that), that's why we disable it.
I made PR to rails(rails/rails#15608) about this obsolete documentation. Let's look what will say rails maintainers.
My quote from this PR, why skip_before_action :verify_authenticity_token
should be deleted:
:null_session was discussed in #5326. Quote from there:
null_session is another change we'd discussed, instead of resetting the session it should just assign the cookie and session objects to values which return null for everything and then send no Set-Cookie headers in the response.
Look at method
handle_unverified_request
In case you call
skip_before_action :verify_authenticity_token
,handle_unverified_request
method ofNullSession
class wouldn't be called. It's mean that session variable will be real session and all data populated to session will be stored for subsequent requests. That's means that api will be vulnerable for CSRF attacks.But if you delete
skip_before_action :verify_authenticity_token
line. session will be NullSessionHash and all data populated to session will be lost for subsequent requests
All the ideas being made @gonzalo-bulnes @donbobka are useful but it seems they highly depend on the use case. For a non-browser client it seems disabling csrf is standard but it turns out that it is not advised when a browser is consuming the json api. See: this and this
So I believe the options for a client agnostic api are:
-
As @gonzalo-bulnes has mentioned the simplest way would be to allow csrf and the devise fallback. Then send the csrf token with every json request from the browser and mobile client. Extracting the csrf token from rails/devise on something like android/ios requires some work.
-
Disable csrf protection and disable the devise fallback. Implement access restriction when token auth fails or current_user is missing. And finally disable cookie session storage and devise sending cookies to the clients with
config.skip_session_storage
. Might be code leading to vulnerabilities. -
Follow @donbobka steps that keep csrf and devise fallback while using
protect_from_forgery with: :null_session
but not usingskip_before_action :verify_authenticity_token, if: :json_request
. This way rails will destroy a session without a csrf token without raising an exception. See: this
I will probably go with option 3 since it preserves the simple_token_authentication and devise gems especially with their community vetting.
Thanks for the help and guidance!
PS: With this method is there a problem with devise sending session cookies on registration and login or when devise actually comes into play when current_user
is missing @donbobka ?
This way rails will destroy a session without a csrf token without raising an exception.
In this way rails works if you use protect_from_forgery with: :reset_session
. As i mentioned before :null_session
keeps your session and cookies untouched, at the same time for your api requests (all actions under protect_from_forgery with: :null_session
) it provides an empty session (here) and empty cookies (here) and skip session update step (here).
PS: With this method is there a problem with devise sending session cookies on registration and login or when devise actually comes into play when current_user is missing @donbobka ?
Due to :null_session
provides to your request empty session, devise have to authorize user with email and token every request. If pair email/token is invalid or absent, current_user would be missing, therefore authenticate_user!
method of devise
throws a error.
@gertfindel
Hi @gonzalo-bulnes
This configuration is correct for for Grape gem?
My application run for http and api mode. Rails 4.2.4 Devise 3.5.6, simple_token_authentication 1.12.0, grape 0.14.0
config/initializers/simple_token_authentication.rb without modification
My User model:
class User < ActiveRecord::Base
acts_as_token_authenticatable
devise :database_authenticatable,
:recoverable,
:timeoutable,
:registerable,
:confirmable,
:trackable,
:validatable,
:lockable,
:password_expirable,
:secure_validatable,
:password_archivable,
:expirable,
:authentication_keys => [:email]
#gem 'devise_security_extension'
#devise :password_expirable,
# :secure_validatable,
# :password_archivable,
# :session_limitable,
# :expirable
....
end
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
protect_from_forgery with: :null_session, :if => Proc.new { |c| c.request.format == 'application/json' }
acts_as_token_authentication_handler_for User, fallback: :none
....
end
and routes.rb
mount API::Base, at: "/"
end
inside app/controllers/api/v1/certificates.rb
module API
module V1
class Certificates < Grape::API
include API::V1::Defaults
#include Devise::Controllers::Helpers
acts_as_token_authentication_handler_for User
resource :certificates do
desc "Return all certificates"
get "", root: :certificates do
authenticate_user!
Certificate.all
end
end
end
end
end
end
..and WEBrick (when run) show error:
/home/xxxxxxx/my_app/app/controllers/api/v1/certificates.rb:8:in `<class:Certificates>': undefined method `acts_as_token_authentication_handler_for' for API::V1::Certificates:Class (NoMethodError)
if uncomment include Devise::Controllers::Helpers
...this error show to :(
What is wrong?
Hello @BSorbus,
The Grape support is work in progress (and I need help with it from someone familiar with Grape!) The intent is described in the wiki, progress is coordinated from #138 (there are intructions there to use the experimental code), and the current bottleneck is described in #194.
I suggest you start from #194, as it contains links to the most relevant comments in #138. Please let me know in #138 if you experiment difficulties using the experimental branch, and if you are able to suggest how the Simple Token Authentication methods / callbacks should be distributed between Grape::API
and Grape::Endpoint
so the usage feels convenient to Grape users.
Thanks for the information. I wish ( myself too :) ) the expeditious completion of this adapter :)
The auth_token gets created in the database when we try to create the user, how should we stop this to generate auth_token only at login POST call.
Hello @ratneshnavlakhe,
Can you open a new issue with your question please? You can just copy your comment in a new issue, give it a title and I'll reply to you there!
Keeping topics separate makes easier for anyone having the same question to find yours. Thank you!