simple_token_authentication icon indicating copy to clipboard operation
simple_token_authentication copied to clipboard

Q: What is the simple_token_authentication configuration for an api?

Open Rotimi opened this issue 10 years ago • 15 comments

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:

  1. config.sign_in_token = false

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

Rotimi avatar Jun 10 '14 00:06 Rotimi

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 the README).
  • 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 the require_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.

gonzalo-bulnes avatar Jun 10 '14 02:06 gonzalo-bulnes

@Rotimi, IMO the best way of gem usage for api following:

  1. 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
  2. config/initializers/simple_token_authentication.rb without modification
  3. 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

donbobka avatar Jun 10 '14 07:06 donbobka

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

gonzalo-bulnes avatar Jun 10 '14 12:06 gonzalo-bulnes

For reference: #45

gonzalo-bulnes avatar Jun 10 '14 12:06 gonzalo-bulnes

IMO this documentation(Rails request forgery protection documentation) is obsolete. Because without verify_authenticity_token call protect_from_forgery with: :null_session do nothing.

donbobka avatar Jun 10 '14 12:06 donbobka

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.

gonzalo-bulnes avatar Jun 10 '14 13:06 gonzalo-bulnes

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 of NullSession 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

donbobka avatar Jun 10 '14 17:06 donbobka

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:

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

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

  3. Follow @donbobka steps that keep csrf and devise fallback while using protect_from_forgery with: :null_session but not using skip_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 ?

Rotimi avatar Jun 11 '14 00:06 Rotimi

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.

donbobka avatar Jun 11 '14 05:06 donbobka

@gertfindel

gonzalo-bulnes avatar Jun 11 '14 12:06 gonzalo-bulnes

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?

BSorbus avatar Feb 01 '16 13:02 BSorbus

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.

gonzalo-bulnes avatar Feb 01 '16 13:02 gonzalo-bulnes

Thanks for the information. I wish ( myself too :) ) the expeditious completion of this adapter :)

BSorbus avatar Feb 01 '16 15:02 BSorbus

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.

ratneshnavlakhe avatar Jan 05 '17 10:01 ratneshnavlakhe

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!

gonzalo-bulnes avatar Jan 05 '17 21:01 gonzalo-bulnes