Code Monkey home page Code Monkey logo

simple_token_authentication's Introduction

Simple Token Authentication

Gem Version Build Status Code Climate Inline docs

Token authentication support has been removed from Devise for security reasons. In this gist, Devise's José Valim explains how token authentication should be performed in order to remain safe (see important warning below).

This gem packages the content of the gist and provides a set of convenient options for increased flexibility.

DISCLAIMER: I am not José Valim, nor has he been involved in the gem bundling process. Implementation errors, if any, are mine; and contributions are welcome. -- GB

Security notice

Last independent audit

Security notice: As the name of the gem indicates, it provides a very basic mechanism for token authentication. If your tokens are not discarded after a single use, or you don't know how to mitigate replay attacks, then you should look at alternatives. (Simple Token Authentication doesn't mitigate those attacks for you.)

In other words: if you don't know why Simple Token Authentication is safe to use in your specific use case, then it probably isn't.

So... what does the gem do? Simple Token Authentication allows to generate, revoke, and safely compare tokens for authentication purposes. That's not the only thing you need to implement a safe authentication protocol, but it can be a part of it.

Personal note: I've used the gem to manage single-use sign-in links sent by email (that's what I created it for). I would use it again for that purpose. Please do your research and check carefully if this tool is adequate to your level of experience and threat model. -- GB

Installation

In a nutshell

First install Devise and configure it with any modules you want, then add the gem to your Gemfile and bundle install:

# Gemfile

gem 'simple_token_authentication', '~> 1.0' # see semver.org

Once that done, only two steps are required to setup token authentication:

  1. Make one or more models token authenticatable (ActiveRecord and Mongoid are supported)
  2. Allow controllers to handle token authentication (Rails, Rails API, and ActionController::Metal are supported)

If you want more details about how the gem works, keep reading! We'll get to these two steps after the overview.

Overview

Simple Token Authentication provides the ability to manage an authentication_token from your model instances. A model with that ability enabled is said to be token authenticatable (typically, the User model will be made token authenticatable).

The gem also provides the ability for any controller to handle token authentication for one or multiple token authenticatable models. That ability allows, for example, to automatically sign in an user when the correct credentials are provided with a request. A controller with that ability enabled is said to behave as a token authentication handler. The token authentication credentials for a given request can be provided either in the form of query params, or HTTP headers. By default, the required credentials are the user's email and their authentication token.

What happens when a request is provided with no credentials or incorrect credentials is highly configurable (some scenarios may require access to be denied, other may allow unauthenticated access, or provide others strategies to authenticate users). By default, when token authentication fails, Devise is used as a fallback to ensure a consistent behaviour with controllers that do not handle token authentication.

Make models token authenticatable

ActiveRecord

First define which model or models will be token authenticatable (typ. User):

# app/models/user.rb

class User < ActiveRecord::Base
  acts_as_token_authenticatable

  # Note: you can include any module you want. If available,
  # token authentication will be performed before any other
  # Devise authentication method.
  #
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :invitable, :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable,
         :lockable

  # ...
end

If the model or models you chose have no :authentication_token attribute, add them one (with a unique index):

rails g migration add_authentication_token_to_users "authentication_token:string{30}:uniq"
rake db:migrate

Mongoid

Define which model or models will be token authenticatable (typ. User):

# app/models/user.rb

class User
  include Mongoid::Document
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  ## Token Authenticatable
  acts_as_token_authenticatable
  field :authentication_token

  # ...
end

Allow controllers to handle token authentication

Finally define which controllers will handle token authentication (typ. ApplicationController) for which token authenticatable models:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base # or ActionController::API
                                                     # or ActionController::Metal
  # ...

  acts_as_token_authentication_handler_for User

  # Security note: controllers with no-CSRF protection must disable the Devise fallback,
  # see #49 for details.
  # acts_as_token_authentication_handler_for User, fallback: :none

  # The token authentication requirement can target specific controller actions:
  # acts_as_token_authentication_handler_for User, only: [:create, :update, :destroy]
  # acts_as_token_authentication_handler_for User, except: [:index, :show]
  #
  # Or target specific controller conditions:
  # acts_as_token_authentication_handler_for User, unless: lambda { |controller| controller.request.format.html? }
  # acts_as_token_authentication_handler_for User, if: lambda { |controller| controller.request.format.json? }

  # Several token authenticatable models can be handled by the same controller.
  # If so, for all of them except the last, the fallback should be set to :none.
  #
  # Please do notice that the order of declaration defines the order of precedence.
  #
  # acts_as_token_authentication_handler_for Admin, fallback: :none
  # acts_as_token_authentication_handler_for SpecialUser, fallback: :none
  # acts_as_token_authentication_handler_for User # the last fallback is up to you

  # Aliases can be defined for namespaced models:
  #
  # acts_as_token_authentication_handler_for Customer::Representative, as: :facilitator
  # acts_as_token_authentication_handler_for SpecialUser, as: :user
  #
  # When defined, aliases are used to define both the params and the header names to watch.
  # E.g. facilitator_token, X-Facilitator-Token

  # ...
end

Configuration

Some aspects of the behavior of Simple Token Authentication can be customized with an initializer.

The file below contains examples of the patterns that token authentication handlers will watch for credentials (e.g. user_email, X-SuperAdmin-Token) and how to customize them:

# config/initializers/simple_token_authentication.rb

SimpleTokenAuthentication.configure do |config|

  # Configure the session persistence policy after a successful sign in,
  # in other words, if the authentication token acts as a signin token.
  # If true, user is stored in the session and the authentication token and
  # email may be provided only once.
  # If false, users must provide their authentication token and email at every request.
  # config.sign_in_token = false

  # Configure the name of the HTTP headers watched for authentication.
  #
  # Default header names for a given token authenticatable entity follow the pattern:
  #   { entity: { authentication_token: 'X-Entity-Token', email: 'X-Entity-Email'} }
  #
  # When several token authenticatable models are defined, custom header names
  # can be specified for none, any, or all of them.
  #
  # Note: when using the identifiers options, this option behaviour is modified.
  # Please see the example below.
  #
  # Examples
  #
  #   Given User and SuperAdmin are token authenticatable,
  #   When the following configuration is used:
  #     `config.header_names = { super_admin: { authentication_token: 'X-Admin-Auth-Token' } }`
  #   Then the token authentification handler for User watches the following headers:
  #     `X-User-Token, X-User-Email`
  #   And the token authentification handler for SuperAdmin watches the following headers:
  #     `X-Admin-Auth-Token, X-SuperAdmin-Email`
  #
  #   When the identifiers option is set:
  #     `config.identifiers = { super_admin: :phone_number }`
  #   Then both the header names identifier key and default value are modified accordingly:
  #     `config.header_names = { super_admin: { phone_number: 'X-SuperAdmin-PhoneNumber' } }`
  #
  # config.header_names = { user: { authentication_token: 'X-User-Token', email: 'X-User-Email' } }

  # Configure the name of the attribute used to identify the user for authentication.
  # That attribute must exist in your model.
  #
  # The default identifiers follow the pattern:
  # { entity: 'email' }
  #
  # Note: the identifer must match your Devise configuration,
  # see https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address#tell-devise-to-use-username-in-the-authentication_keys
  #
  # Note: setting this option does modify the header_names behaviour,
  # see the header_names section above.
  #
  # Example:
  #
  #   `config.identifiers = { super_admin: 'phone_number', user: 'uuid' }`
  #
  # config.identifiers = { user: 'email' }

  # Configure the Devise trackable strategy integration.
  #
  # If true, tracking is disabled for token authentication: signing in through
  # token authentication won't modify the Devise trackable statistics.
  #
  # If false, given Devise trackable is configured for the relevant model,
  # then signing in through token authentication will be tracked as any other sign in.
  #
  # config.skip_devise_trackable = true
end

Usage

Tokens Generation

Assuming user is an instance of User, which is token authenticatable: each time user will be saved, and user.authentication_token.blank? it receives a new and unique authentication token (via Devise.friendly_token).

Token Request

Simple Token Authentication only provides the functionality to authenticate a user based on their authentication_token. For example how to setup your controller to get the token at first please check this wiki

Authentication Method 1: Query Params

You can authenticate passing the user_email and user_token params as query params:

GET https://[email protected]&user_token=1G8_s7P-V-4MGojaKD7a

The token authentication handler (e.g. ApplicationController) will perform the user sign in if both are correct.

Authentication Method 2: Request Headers

You can also use request headers (which may be simpler when authenticating against an API):

X-User-Email [email protected]
X-User-Token 1G8_s7P-V-4MGojaKD7a

In fact, you can mix both methods and provide the user_email with one and the user_token with the other, even if it would be a freak thing to do.

Integration with other authentication and authorization methods

If sign-in is successful, no other authentication method will be run, but if it doesn't (the authentication params were missing, or incorrect) then Devise takes control and tries to authenticate_user! with its own modules. That behaviour can however be modified for any controller through the fallback option (which defaults to fallback: :devise).

When fallback: :exception is set, then an exception is raised on token authentication failure. The resulting controller behaviour is very similar to the behaviour induced by using the Devise authenticate_user! callback instead of authenticate_user. That setting allows, for example, to prevent unauthenticated users to accede API controllers while disabling the default fallback to Devise.

Important: Please do notice that controller actions without CSRF protection must disable the Devise fallback for security reasons (both fallback: :exception and fallback: :none will disable the Devise fallback). Since Rails enables CSRF protection by default, this configuration requirement should only affect controllers where you have disabled it specifically, which may be the case of API controllers.

To use no fallback when token authentication fails, set fallback: :none.

Hooks

One hook is currently available to trigger custom behaviour after an user has been successfully authenticated through token authentication. To use it, implement or mixin a module with an after_successful_token_authentication method that will be ran after authentication from a token authentication handler:

# app/controller/application_controller.rb

class ApplicationController < ActiveController::Base
  acts_as_token_authentication_handler_for User

  # ...

  private

    def after_successful_token_authentication
      # Make the authentication token to be disposable - for example
      renew_authentication_token!
    end
end

Testing

Here is an example of how you can test-drive your configuration using Minitest:

class SomeControllerTest < ActionController::TestCase

  test "index with token authentication via query params" do
    get :index, { user_email: "[email protected]", user_token: "1G8_s7P-V-4MGojaKD7a" }
    assert_response :success
  end

  test "index with token authentication via request headers" do
    @request.headers['X-User-Email'] = "[email protected]"
    @request.headers['X-User-Token'] = "1G8_s7P-V-4MGojaKD7a"

    get :index
    assert_response :success
  end
end

Documentation

Frequently Asked Questions

Any question? Please don't hesitate to open a new issue to get help. I keep questions tagged to make possible to review the open questions, while closed questions are organized as a sort of FAQ.

Change Log

Releases are commented to provide a brief change log, details can be found in the CHANGELOG file.

Development

Testing and documentation

This gem development has been test-driven since v1.0.0. Until v1.5.1, the gem behaviour was described using Cucumber and RSpec in a dummy app generated by Aruba. Since v1.5.2 it is described using Rspec alone and Appraisal is used since v1.13.0 for regression testing.

RSpec tags are used to categorize the spec examples.

Spec examples that are tagged as public describe aspects of the gem public API, and MAY be considered as the gem documentation.

The private or protected specs are written for development purpose only. Because they describe internal behaviour which may change at any moment without notice, they are only executed as a secondary task by the continuous integration service and SHOULD be ignored.

Run rake spec:public to print the gem public documentation.

Contributions

Contributions are welcome! I'm not personally maintaining any list of contributors for now, but any PR which references us all will be welcome.

Please be sure to review the open issues and contribute with your ideas or code in the issue best suited to the topic. Keeping discussions in a single place makes easier to everyone interested in that topic to keep track of the contributions.

Finally, please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Credits

It may sound a bit redundant, but this gem wouldn't exist without this gist, nor without the comments and contributions of many people. Thank them if you see them!

License

Simple Token Authentication
Copyright (C) 2013‒2022 Gonzalo Bulnes Guilpain

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

simple_token_authentication's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simple_token_authentication's Issues

Rails::API support? (Formerly: Including directly into ActionController::Base)

This is both an issue and a question in one:

Why do you include this in ActionController::Base and not ApplicationController?

This behavior doesn't allow for Rails API to use this gem without hacking at the source/including it in ActionController::API or ApplicationController.

The second downside is that, as far as I can tell Devise is adding it's methods to the ApplicationController as well, which is forcing you to reload the Devise Methods in ActionController::Base.

I might work on this weekend and perhaps send in a PR, but probably needs to be changed regardless if i'm right. Feel free to let me know if I'm totally off the mark though.

Need help with setting up for API - getting undefined method `authenticate_user!' or no authentication

I've read this #67
I've done this

  1. config.sign_in_token = false

  2. acts_as_token_authentication_handler_for User, fallback_to_devise: false

Once I do this, I'm able to access any controller without any token provided. It's basically like authentication doesn't exist.

If I don't include fallback_to_devise: false, I get undefined method `authenticate_user!' on every request.
I've read this #42
Yes my User model is setup for devise and I've installed devise.

I'm trying to create an app which is purely an API like explained in issue 67. Feels like I'm missing something major. Thanks in advance for any help.

Here are some relevant files that should help with troubleshooting

class User < ActiveRecord::Base
  acts_as_token_authenticatable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.

  protect_from_forgery with: :null_session#, :if => Proc.new { |c| c.request.format == 'application/json' }

  acts_as_token_authentication_handler_for User, fallback_to_devise: false

end
SimpleTokenAuthentication.configure do |config|

  # Configure the session persistence policy after a successful sign in,
  # in other words, if the authentication token acts as a signin token.
  # If true, user is stored in the session and the authentication token and
  # email may be provided only once.
  # If false, users must provide their authentication token and email at every request.
  config.sign_in_token = false

  # Configure the name of the HTTP headers watched for authentication.
  #
  # Default header names for a given token authenticatable entity follow the pattern:
  #   { entity: { authentication_token: 'X-Entity-Token', email: 'X-Entity-Email'} }
  #
  # When several token authenticatable models are defined, custom header names
  # can be specified for none, any, or all of them.
  #
  # Examples
  #
  #   Given User and SuperAdmin are token authenticatable,
  #   When the following configuration is used:
  #     `config.header_names = { super_admin: { authentication_token: 'X-Admin-Auth-Token' } }`
  #   Then the token authentification handler for User watches the following headers:
  #     `X-User-Token, X-User-Email`
  #   And the token authentification handler for SuperAdmin watches the following headers:
  #     `X-Admin-Auth-Token, X-SuperAdmin-Email`
  #
  # config.header_names = { user: { authentication_token: 'X-User-Token', email: 'X-User-Email' } }

end

How to protect the registrations controller through token authentication?

I am trying to override the registration controller so I can respond with JSON. The issue I am having is that authenticate_scope! is called before [:edit, :update, :destroy] and I need the users to be able to modify their profiles. Is there a good way to handle this with a simple_token_authentication method or something?

Can I use token authentication with an API without sending the e-mail and token in every request? (R: Yes, but maybe you shouldn't.)

Sorry, probably this questions shouldn't be here but I couldn't find any other medium to contact Gonzalo (do you have a twitter account?).

So, the docs says:

Configure the session persistence policy after a successful sign in,

in other words, if the authentication token acts as a signin token.

If true, user is stored in the session and the authentication token and

email may be provided only once.

If false, users must provide their authentication token and email at every request.

config.sign_in_token = false

I'm going to consume my App API with an android client. How can the server know which user is authenticated on which tablet? What should I send the server after the authentication?

Given some external support, the gem should be able to ensure (optionally) that generated tokens never repeat

As a paranoid developer
In order to ensure old tokens can't be used to hijack my API users accounts
I want to be able to provide the location of a list of old tokens for each user
And I want the gem to check that list on sign in so it does not generate twice the same authentication_token for the same user

The idea of this issue takes origin in this description of a token renewal mecanism, which I consider necessary on every token authentication implementation.

Allow passing custom header names

Instead of using the X-User-Email header allow the developer to pass a different header name. In my case I want to use X-User-Username.

I already have some code for it but will finish it off later today. I will try post a link to my commit so you can take a look at it.

remove rails dependency

Devise already defines a railties dependency. The current rails dependency in this gem prevents you from installing this on rails 3.2

The gem defaults should be modificatable via an initializer

As a developer
In order to set the defaults I prefer
And to do it in the way I use to
I want SimpleTokenAuthentication to provide a configuration mecanisme which allow me to configure it from an initializer file
And the initializer to follow the pattern:

# config/initializers/simple_token_authentication.rb
SimpleTokenAuthentication.configure do |config|

  # Option defaults description
  config.option = value
end

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:

  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?

Q: Why doesn't simple_token_authentication provide a Devise strategy?

I hope this not a silly question with a simple answer. When token authentication was built into Devise it was a Devise strategy - https://github.com/plataformatec/devise/blob/v3.0/lib/devise/strategies/token_authenticatable.rb

In simple_token_authentication some patterns are kind of duplicated, for example acts_as_token_authenticatable which could be devise :token_authenticatable, ... or acts_as_token_authentication_handler_for User which duplicates before_action :authenticate_user!

I believe that making simple_token_authentication a strategy will be coherent with other authentication strategies and possibly could simplify testing.

However, current implementation of simple_token_authentication allows specifying token authentication only for selected controllers. I don't know whether it is possible with a Devise strategy (Use strategy A in controller B and strategy C in controller D).

I know that it sounds like a complete redesign of the architecture with backwards incompatible changes but maybe it's worth considering.

Get 401 after a sign in via API

Hello Gonzalo,
I have to migrate an application to devise3 and I'm trying your gem according to your instructions.
I cannot sign in via API, I get a 401 despite a first signin is made, I think there is a second one that generate the issue. If what i presume is correct, any ideas to solve?

Started GET "/api/v1/posts/144015.json" for 127.0.0.1 at 2014-05-06 15:43:57 +0200
  SQL (2.0ms)  USE [DB_DEV]
Processing by Api::V1::PostsController#show as JSON
  Parameters: {"id"=>"144015"}
  User Load (5.0ms)  EXEC sp_executesql N'SELECT TOP (1) [users].* FROM [users]WHERE [users].[email] = N''[email protected]'''
  SQL (2.0ms)  BEGIN TRANSACTION
   (141.0ms)  EXEC sp_executesql N'UPDATE [users] SET [last_sign_in_at] = ''2014-04-30T15:59:28.967'', [current_sign_in_at] = ''2014-05-06T13:43:58.107'', [sign_in_count] = 261, [updated_at] = ''2014-05-06T13:43:58.124'' WHERE [users].[id]= 5; SELECT @@ROWCOUNT AS AffectedRows'
  SQL (8.0ms)  COMMIT TRANSACTION
Completed 401 Unauthorized in 262.0ms

Version 5 in gemfile installs version 4

This is what I have in my Gemfile gem 'simple_token_authentication', '~> 1.0.0.beta.5' and i get this when I use bundle install simple_token_authentication (1.0.0.pre.beta.4)

Q: `current_user` is not set

My Api is returning nil for current_user when I pass email and token through the header. My session controller sees current_user when creating and destroying a new session.

module Api
  module V1

    class UsersController < Api::V1::ApplicationController

      include Devise::Controllers::Helpers

      acts_as_token_authentication_handler_for User, fallback_to_devise: false

      def index

        render json: {
                     success: true,
                     auth_token: current_user.authentication_token,
                     email: current_user.email
                   }

      end

    end

  end
end

Some errors with namespaced classes.

I had some trouble using this gem with namespaced classed.

I think the reason is because the underscore inflector is used throughout the code and converts classes like "Core::User" to 'core/user' which might be invalid syntax.

One example is line 120 of acts_as_token_authentication_handler.rb.

      def define_acts_as_token_authentication_helpers_for(entity_class)
        entity_underscored = entity_class.name.singularize.underscore

        class_eval <<-METHODS, __FILE__, __LINE__ + 1
          def authenticate_#{entity_underscored}_from_token
            authenticate_entity_from_token!(#{entity_class.name})
          end

Another example might be line 70: X-#{entity_class.name.singularize.camelize}-Token"
this would look for a token called "X-Core::User-Token" which might not be valid syntax. Not sure...

Anyway, I got it working by using the devise strategy branch and specifying the header names in devise.rb initializer.

Oh, and thanks so much for this gem!!!

Delegating to devise's built-in authenticate_user! introduces security risk in API context

I'm building a JSON API with an Ember front-end. Therefore, I want to be able to turn of CSRF validation for JSON requests, as references a number of times in previous issues. If I do that, requiring authentication tokens is the only way to secure JSON requests.

However, based on what I can see in https://github.com/gonzalo-bulnes/simple_token_authentication/blob/master/lib/simple_token_authentication/acts_as_token_authentication_handler.rb#L23-L26, this gem delegates to Devise's default authentication mechanism after doing token authentication.

Devise's default authentication makes every request fallback to cookie authentication. That means any existing users will still be using cookies until they sign out & sign back in. Also, when I disable CSRF validation on JSON requests, I'll still be vulnerable to CSRF attacks. The reason I want token authentication is so I can force clients to send identifying information with every request. Falling back to devise's authentication seems to defeat that purpose.

I can think of a few possible ways to solve this:

  1. Remove the fallback entirely, but that might not work well for existing users
  2. Only fallback for non-JSON requests by default (maybe with an opt-in for the previous behavior)
  3. Disable fallback for all requests with opt-in to fallback on all requests

I'm thinking I might implement option 2 and send a PR. @gonzalo-bulnes any thoughts on this?

The gem requires testing.

As a developer
In order to prevent dramas
And to learn about modules testing
I want the gem to contain a decent set of tests before its v1.0.0 is released

Any kind of help will be very appreciated! : )

401 unauthorized errors for Session#destroy action

I'm running into an issue where Login via a JSON API using Simple Token Auth works, token authentication works, but I am always getting 401 authentication issues with the logout call.

I'm working on a couple of Rspec tests to demonstrate the issue. The authentication via token works every where else (test suite and in real life). It's just logout where I constantly get a 401.

I have Devise configured to accept logout via GET (rather than DELETE). Otherwise, my issue sounds similar to this recent, unanswered thread: http://stackoverflow.com/questions/24092791/ruby-on-rails-curl-delete-token-authentication

Does this sound like a Devise issue or a Simple Token Auth issue?

Two failing test cases are below.

Any and all thoughts would be greatly appreciated!

app/controllers/api/v1/api_controller.rb

class Api::V1::ApiController < ApplicationController
  acts_as_token_authentication_handler_for User, fallback_to_devise: false
  before_action :authenticate_user!
  skip_filter :authenticate_user!, only: [:connection_test]

  respond_to :json

  protect_from_forgery with: :null_session
end

app/controllers/api/v1/sessions_controller.rb

class Api::V1::SessionsController < Devise::RegistrationsController
  prepend_before_filter :require_no_authentication, only: [:create]
  skip_filter :authenticate_entity_from_token!, only: [:create]
  skip_filter :authenticate_user!, only: [:create]
  before_action :ensure_params_exist
  skip_before_action :verify_authenticity_token

  def create
    build_resource
    resource = User.find_for_database_authentication(
      email: params[:user][:email]
    )
    return invalid_login_attempt unless resource

    if resource.valid_password?(params[:user][:password])
      sign_in('user', resource)
      render json: {
        success: true,
        auth_token: resource.authentication_token,
        email: resource.email,
        user_id: resource.id,
        restaurant_id: resource.restaurant.blank? ? nil : resource.restaurant.id,
        driver_id: resource.driver.blank? ? nil : resource.driver.id
      }
      return
    end
    invalid_login_attempt
  end

  def destroy
    sign_out(resource_name)
  end

  protected

  def ensure_params_exist
    return unless params[:user].blank?
    render status: 422, json: {
      success: false,
      message: 'missing user parameter'
    }
  end

  def invalid_login_attempt
    warden.custom_failure!
    render status: 401, json: {
      success: false,
      message: 'Error with your login or password'
    }
  end
end

config/routes.rb

  devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout', sign_up: 'register', edit: 'account' }

  namespace :api do
    namespace :v1, constraints: { format: 'json' }, defaults: { format: :json } do
      devise_for :users, path: 'users', path_names: { sign_in: 'login', sign_out: 'logout', sign_up: 'register', edit: 'account' }
      ...
    end
  end

spec/controllers/api/v1/sessions_controller_spec.rb

require 'spec_helper'

describe Api::V1::SessionsController do
  before(:each) { @request.env["devise.mapping"] = Devise.mappings[:user] }
  ...

  describe 'GET logout' do
    context 'for drivers' do
      it 'logs out drivers' do
        request.accept = 'application/json'

        resource = create(:driver)
        @user = resource.user

        # setup the api authentication headers
        request.headers['X-User-Email'] = @user.email
        request.headers['X-User-Token'] = @user.authentication_token

        get :destroy
        expect(response.status).to eq(200)
      end
    end

    context 'for restaurants' do
      it 'logs out restaurants' do
        request.accept = 'application/json'

        resource = create(:restaurant)
        @user = resource.user

        # setup the api authentication headers
        request.headers['X-User-Email'] = @user.email
        request.headers['X-User-Token'] = @user.authentication_token

        get :destroy
        expect(response.status).to eq(200)
      end
    end
  end
end

Should I use the Devise Registrable module? (R: Yes!)

More of a question than an issue. I noticed you posted how you would override devise session controller to handle json responses (https://gist.github.com/gonzalo-bulnes/9001010).

What is the strategy for user registration with this gem? Does it make sense to use normal devise controller? Should I just create one that creates a user and all token mechanics will work fine because of the ensure_token on user save? What do you suggest for this?

Use .exists? instead of .first

Looking at the code I noticed you use .first to check if token is available (in generate_authentication_token). Wouldn't exists? be a better fit?

def generate_authentication_token
  loop do
    token = Devise.friendly_token
    break token unless self.class.where(authentication_token: token).exists?
  end
end

How to change the JSON response message?

How can I change the JSON response when the user logs in with the incorrect email or password? Right now the JSON response is:
{"error":"Invalid email or password."}

I need to change it so it is consistent with the rest of my API. I need it to be:

{"success":false, "info":"you need to be a paying customer", "data":{}}

Getting an undefined local variable or method `current_user' error? (R: Check first the Devise setup.)

hi @gonzalo-bulnes ,

i'm trying to get the gem working for the api part of my app.

using

gem 'simple_token_authentication'

i set up

class Api::V1::BaseController < ActionController::Base
    acts_as_token_authentication_handler_for User, fallback_to_devise: false
end 

and

  config.sign_in_token = false

i manage to login using request headers and i reach the controller action

i'm not using

before_filter :authenticate_user! 

but i get

undefined local variable or method `current_user'

am i missing something? do you need more info?

thank's

undefined local variable or method `current_user'

hi @gonzalo-bulnes ,

while trying to get the gem working for the api part of my app

this is my base controller :

class Api::V1::BaseController < ActionController::Base
    acts_as_token_authentication_handler_for User, fallback_to_devise: false
    end
end

i manage to log in successfully using headers but when getting to the controller action because

before_filter :authenticate_user! 

is not defined i get the error for current user.

am i missing some declaration or a method ?

if you need more info please tell me.

thank's

Expire token supported?

Is the token expirable?

I didn't see any configuration to achieve this. Is there a way to do it with simple_token_auth?

Thanks in advance!

Kevin

Getting an ActionController::InvalidAuthenticityToken exception? (R: Take a look at CSRF answers!)

I am having the strangest issue. I am only having successful authentications on GET's. I am passing the email and token via headers, not params: X-User-Email It works on GET, but not on PUT or PATCH.

When I run a debugger it goes to the authenticate_entity_from_token! method only on GET. But on a PATH or PUT I get a ActionController::InvalidAuthenticityToken
and it does not go to the authenticate_entity_from_token! method.

My routes are regular REST resources. It is a simple Rails 4 app with a single model just for testing.

This gem forces authentication on all controllers

It took me a while to realize this gem was at fault, but after I added it to my Rails project I could no longer access my public pages -- it was forcing me to the login page instead. The reason is ActionController::Base.send :include, SimpleTokenAuthentication::ActsAsTokenAuthenticationHandler automatically includes the before_filter :authenticate_user_from_token! macro into ActionController::Base which all controllers inherit from. Inclusion of these macros shouldn't happen until I add acts_as_token_authentication_handler to one of my controllers myself.

Any way to skip authentication when you use this?

I have a single route that I have skipped authentication on in Devise using the except: syntax in the relevant controller. But when I apply this token authentication technique in my Application Controller, that endpoint just shows the "You are being redirected" link instead of the public content. Any tips on this?

Thanks,

Walter

Overriding Registration Controller

I saw someone already posted on trying to do this, but i've had no luck. It seems when I try to go to the users/edit page (edit registration), I get an unauthorized error, even though the token and email are passed in. Is there a way to override the registration controller without it using it's normal authenticate_scope! and using the token authentication method instead?

Looking for a JSON SessionsController? (R: Here's some code!)

I know this has been discussed here and here (where I pasted a gist for my custom Sessions).

I was thinking it was all going alright, but I now realize it is not. When trying to logout with the wrong token, I get the following error:

Completed 500 Internal Server Error in 125ms

NoMethodError (undefined method `authentication_token=' for nil:NilClass):
  app/controllers/sessions_controller.rb:18:in `destroy'

I am able to create a session, the token is returned and I can logout successfully when the token is correct. When I pass the wrong token, however, I get that error.

Shouldn't I get a Completed 401 Unauthorized in 5ms just like I do when I try checking a dummy page I created which inherits from ApiController (which uses the same acts_as_token_authentication_handler_for User instruction)?

Thanks,
Daniel

Q: Authenticating two models in one controller (R: One handler each; fallback_to_devise disabled for the first.)

I have a use case where I have two models: a Devise-CAS-authenticated User and a token-authenticated Partner. The user should be able to crud partners, while a partner is only allowed to show his page and post to a custom controller action.

I was thinking something along the lines of:

class PartnersController < ApplicationController

    acts_as_token_authentication_handler_for Partner
    before_action :authenticate_partner!, only: [:show, :confirm]
    before_action :authenticate_user!

    [controller actions]
end

The problem here however is that when I navigate as an authenticated User to the index method, my access gets denied. This is because (correct me if I'm wrong) the acts_as_token_authentication_handler already includes the before_action authenticate_partner! as a fallback if the token authentication fails, causing the authentication to fail, and throwing the 403 error.

Is there a way around this or is it possible to restrict the acts_as_token_authentication_handler to certain controller actions?

Thanks in advance.

How to add token authentication to an API?

I'm trying to add a secure API for my rails app, which it uses Devise for the user authentication and a mobile app will be able to use this API.

I'm following this tutorial: http://lucatironi.github.io/tutorial/2012/10/15/ruby_rails_android_app_authentication_devise_tutorial_part_one/ but it uses the deprecated authentication token.

My question is if I install this gem, and configure the models and controllers, I will be able to use the old methods like verify_authenticity_token?

Do you know another way or tutorial in which explain how to authenticated in a mobile app through Devise rails app?

Q: Getting error `uninitialized constant Devise` when running the test suite? (R: Here is the MacOS X fix.)

I'm not sure if this goes here. I have forked the repository, and I am trying to modify it.

When I try to run the test suite, I'm getting this error. Devise is in my gem list.

uninitialized constant Devise (NameError)
/simple_token_authentication/spec/dummy/config/initializers/devise.rb:3:in `<top (required)>'

Steps I took
I forked the repository
Cloned it locally
I'm using rvm with ruby 2.1.2 and created a new gem set
I ran bundle
I ran rake

Rails::API support

The rails-api gem uses ActionController::API instead of ActionController::Base for its controllers. Maybe replacing this line for

ActionController::API.send :include, SimpleTokenAuthentication::ActsAsTokenAuthenticationHandler 

should work

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.