Мне сложно понять и правильно внедрить Аутентификацию пользователя в API. Другими словами, у меня есть серьезная проблема, чтобы понять интеграцию API Grape с интерфейсами, такими как Backbone.js, AngularJS или Ember.js.
Я пытаюсь развернуть все разные подходы и много читать об этом, но Google возвращает мне действительно плохие ресурсы, и мне кажется, что нет действительно хорошей статьи по этой теме - Rails и аутентификация пользователя с помощью Devise и front-end frameworks.
Я опишу свой текущий стержень, и я надеюсь, что вы можете предоставить мне некоторые отзывы о моей реализации и, возможно, указать мне в правильном направлении.
Текущая реализация
У меня есть backend Rails API REST со следующим Gemfile (я намеренно сокращу весь код файла)
gem 'rails', '4.1.6'
gem 'mongoid', '~> 4.0.0'
gem 'devise'
gem 'grape'
gem 'rack-cors', :require => 'rack/cors'
В моей текущей реализации есть только API-интерфейсы со следующими маршрутами (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)
Я создал следующую модель 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
В моих контроллерах я создал следующую структуру папок: controllers- > api- > v1, и я создал следующую аутентификацию общего модуля (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
Поэтому каждый раз, когда я хочу убедиться, что мой ресурс будет вызван с помощью токена аутентификации, я просто могу добавить это, вызвав: include API::V1::Authentication
к ресурсу Grape:
module API
module V1
class Posts < Grape::API
include API::V1::Defaults
include API::V1::Authentication
Теперь у меня есть еще один ресурс Grape под названием Users (users.rb), и здесь я реализую методы аутентификации, регистрации и выхода из системы (я думаю, что я смешиваю здесь яблоки с грушами, и я должен извлечь процесс входа/выхода из системы в другой Ресурс винограда - сеанс).
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
Я понимаю, что я представляю здесь тонну кода, и это может не иметь смысла, но это то, что у меня есть сейчас, и я могу использовать authentication_token
для вызовов против моего API, которые защищены модулем Authentication
.
Я чувствую, что это решение не очень хорошо, но я действительно ищу проще, как добиться аутентификации пользователей через API. У меня есть несколько вопросов, которые перечислены ниже.
Вопросы
- Считаете ли вы, что такая реализация опасна, если да, то почему? - Я думаю, что это из-за использования одного токена. Есть ли способ улучшить этот шаблон? Я также видел реализацию с отдельной моделью
Token
, которая имеет время истечения срока действия и т.д. Но я думаю, что это почти похоже на повторное использование колеса, потому что для этой цели я могу реализовать OAuth2. Я хотел бы иметь более легкое решение. - Хорошей практикой является создание нового модуля для аутентификации и включение его только в ресурсы, где это необходимо?
- Знаете ли вы о каком-либо хорошем учебнике по этой теме? Rails + Devise + Grape? Кроме того, вы знаете о каких-либо хороших проект Rails с открытым исходным кодом, который реализован таким образом?
- Как я могу реализовать его с другим подходом, который более безопасен?
Я прошу прощения за такой длинный пост, но я надеюсь, что у большего числа людей будет такая же проблема, и это может помочь мне найти больше ответов на мои вопросы.