葡萄和设计用户认证 [英] User Authentication with Grape and Devise

查看:122
本文介绍了葡萄和设计用户认证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很难理解,并在API中正确实施 用户认证 。换句话说,我有严重的问题要了解Grape API与Frontbone.js,AngularJS或Ember.js等前端框架的集成。



我正在设法解决所有不同的方法,并阅读了很多关于这一点的信息,但是Google返回了我真正的坏资源,在我看来,就像没有真正的好关于这个主题的文章 - 使用Devise和前端框架的Rails和用户身份验证。



我将描述我当前的枢轴,希望你能向我提供有关我的实施的一些反馈意见,也许指向正确的方向。



当前实施



我有后端 Rails REST API ,具有以下 Gemfile (我将有意缩短所有文件代码)

  gem'rails','4.1.6'
gem'mongoid','〜> 4.0.0'
gem'devise'
gem'grape'
gem'rack-cors',:require => 'rack / cors'

我目前的实现只有具有以下路由的API( route.rb ):

  api_base / api API :: Base 
GET / :version / posts(。:format)
GET /:version/posts/:id(.:format)
POST /:version/posts(.:format)
DELETE /:version /posts/:id(.:format)
POST /:version/users/authenticate(.:format)
POST /:version/users/register(.:format)
DELETE / :version / users / logout(。:format)

我创建了以下模型 > user.rb

  class User 
include Mongoid :: Document
devise:database_authenticatable,:可注册,
:可恢复,可记忆,可追踪,可验证

字段:电子邮件,类型:字符串,默认值:
字段:encrypted_pa​​ssword ,键入:String,默认值:

字段:authentication_token,键入:String

before_save:ensure_authentication_token!

def ensure_authentication_token!
self.authentication_token || = generate_authentication_token
end

private

def generate_authentication_token
loop do
token = Devise.friendly_token
break token除非User.where(authentication_token:token).first
end
end
end

在我的控制器中,我创建了以下文件夹结构: controllers-> api-> v1 ,并且我创建了以下共享模块验证( 认证.rb

 模块API 
模块V1
模块验证
扩展ActiveSupport :: Concern

包括do
在做
错误!(401未经授权,401)除非经过身份验证?
end

helpers do
def warden
env ['warden']
end

def authenticated?
如果warden.authenticated返回true?
params [:access_token]&& @user = User.find_by(authentication_token:params [:access_token])
end

def current_user
warden.user || @user
end
end
end
end
end
end

所以每次当我想要确保我的资源将被使用身份验证令牌调用时,我可以通过调用:$ code> include API :: V1: :认证到Grape资源:

 模块API 
模块V1
类帖子葡萄:: API
包含API :: V1 ::默认值
包含API :: V1 ::验证

现在我有另一个Grape资源,名为Users(users.rb),这里我实现了身份验证,注册和注销的方法(我认为我将梨与梨混合在一起,我应该提取登录/注销过程到另一个Grape资源 - 会话)。

 模块API 
模块V1
类用户& Grape :: API
包含API :: V1 ::默认值

资源:用户执行
desc验证用户并返回用户对象,访问令牌
params do
要求:email,:type => String,:desc => 用户电子邮件
要求:password,:type => String,:desc => 用户密码
end
post'authenticate'do
email = params [:email]
password = params [:password]

如果电子邮件。零?或password.nil?
错误!({:error_code => 404,:error_message =>电子邮件或密码无效),401)
返回
结束

用户= User.find_by(email:email.downcase)
如果user.nil?
错误!({:error_code => 404,:error_message =>电子邮件或密码无效),401)
return
end

if !user.valid_password?(密码)
错误!({:error_code => 404,:error_message =>无效的电子邮件或密码),401)
return
else b $ b user.ensure_authentication_token!
user.save
status(201){status:'ok',token:user.authentication_token}
end
end

desc注册用户并返回用户对象,访问令牌
params do
require:first_name,:type => String,:desc => 名字
要求:last_name,:type => String,:desc => 姓氏
要求:email,:type => String,:desc => 电子邮件
要求:password,:type => String,:desc => 密码
end
post'register'do
user = User.new(
first_name:params [:first_name],
last_name:params [:last_name] ,
密码:params [:password],
电子邮件:params [:email]


如果user.valid?
user.save
return user
else
error!({:error_code => 404,:error_message =>无效的电子邮件或密码。 b $ b end
end

desc退出用户并返回用户对象,访问令牌
params do
require:token,:type => String,:desc => Authenticaiton Token
end
delete'logout'do

user = User.find_by(authentication_token:params [:token])

如果! user.nil?
user.remove_authentication_token!
状态(200)
{
状态:'ok',
令牌:user.authentication_token
}
else
error!({ :error_code => 404,:error_message =>无效的令牌。},401)
end
end
end
end
end
结束

我意识到我在这里提供了大量的代码,可能没有意义,但这是我现在拥有的,我可以使用 authentication_token 对我的API进行调用,这些调用受到认证



我觉得这个解决方案不好,但是我真的很想找到如何通过API实现用户身份验证。我有几个问题,我在下面列出。



问题


  1. 你认为这种实现是否危险,如果是这样,为什么? - 我认为是因为使用了一个令牌。有没有办法改善这种模式?我也看到实现与单独的模型令牌有过期时间等等。但我认为这几乎就像是重新发明轮,因为为此目的我可以实现OAuth2。我想要更轻的解决方案。

  2. 为验证创建新模块是很好的做法,仅将其包含在需要的资源中。

  3. 你知道关于这个主题的任何好的教程 - 实现
    Rails + Devise + Grape?另外,你知道任何一个好的
    开源的Rails项目,这是通过这种方式实现的?

  4. 如何使用更安全的不同方法来实现? li>

对于这么长的帖子,我很抱歉,但我希望更多的人有同样的问题,这可能会帮助我找到更多的答案

解决方案

添加token_authenticable以设计模块(使用devise版本< = 3.2)



在user.rb中添加:token_authenticatable到设计模块列表,它应该如下所示:

  class User< ActiveRecord :: Base 
#..code ..
devise:database_authenticatable,
:token_authenticatable,
:invitable,
:可注册,
:可恢复,
:可记忆,
:可追踪,
:可验证

attr_accessible:name,:email,,authentication_token

before_save:ensure_authentication_token
#..code ..
end

自己生成身份验证令牌(如果设计版本> 3.2)

  class User< ActiveRecord :: Base 
#..code ..
devise:database_authenticatable,
:invitable,
:可注册,
:可恢复,
:可记忆,
:可追踪,
:可验证

attr_accessible:name,:email,,authentication_token

before_save:ensure_authentication_token

def确保_authentication_token
self.authentication_token || = generate_authentication_token
end

private

def generate_authentication_token
loop do
token = Devise。 friendly_token
break token除非User.where(authentication_token:token).first
end
end

为验证令牌添加迁移

  rails g migration add_auth_token_to_users 
invoke active_record
创建db / migrate / 20141101204628_add_auth_token_to_users.rb

编辑迁移文件以添加:authenti对用户的阳离子列为

  class AddAuthTokenToUsers< ActiveRecord :: Migration 
def self.up
change_table:users do | t |
t.string:authentication_token
end

add_index:users,:authentication_token,:unique => true
end

def self.down
remove_column:users,:authentication_token
end
end
/ pre>

运行迁移



rake db:migrate



为现有用户生成令牌



我们需要在每个确保身份验证的用户实例上调用保存



User.all.each(&:save)



使用认证令牌安全的Grape API



您需要添加以下代码到API :: Root,以添加令牌基于身份验证。如果您不知道API :: Root,请阅读使用葡萄构建RESTful API



在下面的示例中,我们基于两种情况验证用户 - 如果用户登录到Web应用程序,则使用相同的会话 - 如果会话不可用,并且auth令牌被传递,则基于令牌

 #lib / api / root查找用户。 rb 
模块API
class Root<葡萄:: API
前缀'api'
格式:json

rescue_from:all,,backtrace => true
error_formatter:json,API :: ErrorFormatter

before do
error!(401 Unauthorized,401)除非经过验证
end

helpers做
def warden
env ['warden']
end

def authenticated
如果warden.authenticated返回true?
params [:access_token]&& @user = User.find_by_authentication_token(params [:access_token])
end

def current_user
warden.user || @user
end
end

mount API :: V1 :: Root
mount API :: V2 :: Root
end
end


I have difficulties to understand and also properly implement User Authentication in APIs. In other words, I have serious problem to understand the integration of Grape API with front-end frameworks such as Backbone.js, AngularJS or Ember.js.

I'm trying to pivot all different approaches and read a lot about that, but Google returns me truly bad resources and it seems to me, like there is no really good article on this topic - Rails and User authentication with Devise and front-end frameworks.

I will describe my current pivot and I hope you can provide me some feedback on my implementation and maybe point me to the right direction.

Current implementation

I have backend Rails REST API with following Gemfile(I will purposely shorten all file code)

gem 'rails', '4.1.6'
gem 'mongoid', '~> 4.0.0'
gem 'devise'
gem 'grape'
gem 'rack-cors', :require => 'rack/cors'

My current implementation has only APIs with following Routes(routes.rb):

api_base      /api        API::Base
     GET        /:version/posts(.:format)
     GET        /:version/posts/:id(.:format)
     POST       /:version/posts(.:format)
     DELETE     /:version/posts/:id(.:format)
     POST       /:version/users/authenticate(.:format)
     POST       /:version/users/register(.:format)
     DELETE     /:version/users/logout(.:format)

I created have following model user.rb

class User
  include Mongoid::Document
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  field :email,              type: String, default: ""
  field :encrypted_password, type: String, default: ""

  field :authentication_token,  type: String

  before_save :ensure_authentication_token!

  def ensure_authentication_token!
    self.authentication_token ||= generate_authentication_token
  end

  private

  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end   
end

In my controllers I created following folder structure: controllers->api->v1 and I have created following shared module Authentication (authentication.rb)

module API
  module V1
    module Authentication
      extend ActiveSupport::Concern

      included do
        before do
           error!("401 Unauthorized", 401) unless authenticated?
         end

         helpers do
           def warden
             env['warden']
           end

           def authenticated?
             return true if warden.authenticated?
             params[:access_token] && @user = User.find_by(authentication_token: params[:access_token])
           end

           def current_user
             warden.user || @user
           end
         end
       end
     end
   end
end

So every time when I want to ensure, that my resource will be called with Authentication Token, I can simply add this by calling: include API::V1::Authentication to the Grape resource:

module API
  module V1
    class Posts < Grape::API
      include API::V1::Defaults
      include API::V1::Authentication

Now I have another Grape resource called Users(users.rb) and here I implement methods for authentication, registration and logout.(I think that I mix here apples with pears, and I should extract the login/logout process to another Grape resource - Session).

module API
  module V1
    class Users < Grape::API
      include API::V1::Defaults

      resources :users do
        desc "Authenticate user and return user object, access token"
        params do
           requires :email, :type => String, :desc => "User email"
           requires :password, :type => String, :desc => "User password"
         end
         post 'authenticate' do
           email = params[:email]
           password = params[:password]

           if email.nil? or password.nil?
             error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
             return
           end

           user = User.find_by(email: email.downcase)
           if user.nil?
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
              return
           end

           if !user.valid_password?(password)
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
              return
           else
             user.ensure_authentication_token!
             user.save
             status(201){status: 'ok', token: user.authentication_token }
           end
         end

         desc "Register user and return user object, access token"
         params do
            requires :first_name, :type => String, :desc => "First Name"
            requires :last_name, :type => String, :desc => "Last Name"
            requires :email, :type => String, :desc => "Email"
            requires :password, :type => String, :desc => "Password"
          end
          post 'register' do
            user = User.new(
              first_name: params[:first_name],
              last_name:  params[:last_name],
              password:   params[:password],
              email:      params[:email]
            )

            if user.valid?
              user.save
              return user
            else
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
            end
          end

          desc "Logout user and return user object, access token"
           params do
              requires :token, :type => String, :desc => "Authenticaiton Token"
            end
            delete 'logout' do

              user = User.find_by(authentication_token: params[:token])

              if !user.nil?
                user.remove_authentication_token!
                status(200)
                {
                  status: 'ok',
                  token: user.authentication_token
                }
              else
                error!({:error_code => 404, :error_message => "Invalid token."}, 401)
              end
            end
      end
    end
  end
end

I realize that I present here a ton of code and it might not make sense, but this is what I currently have and I'm able to use the authentication_token for calls against my API which are protected by module Authentication.

I feel like this solution is not good, but I really looking for easier way how to achieve user authentication through APIs. I have several questions which I listed below.

Questions

  1. Do you think this kind of implementation is dangerous, if so, why? - I think that it is, because of the usage of one token. Is there a way how to improve this pattern? I've also seen implementation with separate model Token which has expiration time, etc. But I think this is almost like reinventing wheel, because for this purpose I can implement OAuth2. I would like to have lighter solution.
  2. It is good practice to create new module for Authentication and include it only into resources where it is needed?
  3. Do you know about any good tutorial on this topic - implementing Rails + Devise + Grape? Additionally, do you know about any good open-source Rails project, which is implemented this way?
  4. How can I implement it with different approach which is more safer?

I apologize for such a long post, but I hope that more people has the same problem and it might help me to find more answers on my questions.

解决方案

Add token_authenticable to devise modules (works with devise versions <=3.2)

In user.rb add :token_authenticatable to the list of devise modules, it should look something like below:

class User < ActiveRecord::Base
# ..code..
  devise :database_authenticatable,
    :token_authenticatable,
    :invitable,
    :registerable,
    :recoverable,
    :rememberable,
    :trackable,
    :validatable

  attr_accessible :name, :email, :authentication_token

  before_save :ensure_authentication_token
# ..code..
end

Generate Authentication token on your own (If devise version > 3.2)

class User < ActiveRecord::Base
# ..code..
  devise :database_authenticatable,
    :invitable,
    :registerable,
    :recoverable,
    :rememberable,
    :trackable,
    :validatable

  attr_accessible :name, :email, :authentication_token

  before_save :ensure_authentication_token

  def ensure_authentication_token
    self.authentication_token ||= generate_authentication_token
  end

  private

  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end

Add migration for authentiction token

rails g migration add_auth_token_to_users
      invoke  active_record
      create    db/migrate/20141101204628_add_auth_token_to_users.rb

Edit migration file to add :authentication_token column to users

class AddAuthTokenToUsers < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.string :authentication_token
    end

    add_index  :users, :authentication_token, :unique => true
  end

  def self.down
    remove_column :users, :authentication_token
  end
end

Run migrations

rake db:migrate

Generate token for existing users

We need to call save on every instance of user that will ensure authentication token is present for each user.

User.all.each(&:save)

Secure Grape API using auth token

You need to add below code to the API::Root in-order to add token based authentication. If you are unware of API::Root then please read Building RESTful API using Grape

In below example, We are authenticating user based on two scenarios – If user is logged on to the web app then use the same session – If session is not available and auth token is passed then find user based on the token

# lib/api/root.rb
module API
  class Root < Grape::API
    prefix    'api'
    format    :json

    rescue_from :all, :backtrace => true
    error_formatter :json, API::ErrorFormatter

    before do
      error!("401 Unauthorized", 401) unless authenticated
    end

    helpers do
      def warden
        env['warden']
      end

      def authenticated
        return true if warden.authenticated?
        params[:access_token] && @user = User.find_by_authentication_token(params[:access_token])
      end

      def current_user
        warden.user || @user
      end
    end

    mount API::V1::Root
    mount API::V2::Root
  end
end

这篇关于葡萄和设计用户认证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆