ruby-on-rails api ruby-on-rails-4 devise grape

ruby on rails - Autenticación de usuario con uva y diseño



ruby-on-rails api (2)

Agregue token_authenticable para diseñar módulos (funciona con versiones de diseño <= 3.2)

En user.rb agregue: token_authenticatable a la lista de módulos de diseño, debería verse algo como a continuación:

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

Generar un token de autenticación por su cuenta (si es una versión> 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

Agregar migración para token de autentificación

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

Edite el archivo de migración para agregar: columna authentication_token a los usuarios

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

Ejecutar migraciones

rake db:migrate

Generar token para usuarios existentes

Necesitamos llamar a guardar en cada instancia de usuario que garantizará que el token de autenticación esté presente para cada usuario.

User.all.each(&:save)

API segura de uva utilizando token de autenticación

Debe agregar el código siguiente a API :: Root para agregar la autenticación basada en token. Si no está al tanto de API :: Root, por favor lea Building RESTful API using Grape

En el ejemplo siguiente, estamos autenticando al usuario según dos escenarios: si el usuario ha iniciado sesión en la aplicación web, utilice la misma sesión: si la sesión no está disponible y se pasa el token de autorización, busque el usuario en función del 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

Tengo dificultades para comprender y también implementar correctamente la Autenticación de usuario en las API. En otras palabras, tengo un serio problema para entender la integración de la API de Grape con frameworks frontales como Backbone.js, AngularJS o Ember.js.

Estoy tratando de hacer pivotar todos los enfoques diferentes y leer mucho sobre eso, pero Google me devuelve recursos realmente malos y me parece que no hay un artículo realmente bueno sobre este tema: Rails y autenticación de usuario con Devise y front-end marcos .

Describiré mi pivote actual y espero que puedan brindarme algunos comentarios sobre mi implementación y tal vez apuntarme en la dirección correcta.

Implementación actual

Tengo la API REST de Rails de respaldo con el siguiente Gemfile (voy a acortar deliberadamente todo el código de archivo)

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

Mi implementación actual solo tiene API con las siguientes rutas ( 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)

Creé tener el siguiente modelo 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

En mis controladores creé la siguiente estructura de carpetas: controladores-> api-> v1 y he creado la siguiente autenticación de módulo compartido ( 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

Así que cada vez que quiero asegurarme de que se invocará mi recurso con el token de autenticación, simplemente puedo agregarlo llamando al: include API::V1::Authentication al recurso de uva:

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

Ahora tengo otro recurso de Grape llamado Users (users.rb) y aquí implemento métodos para autenticación, registro y cierre de sesión. (Creo que mezclo aquí manzanas con peras, y debería extraer el proceso de inicio de sesión / cierre de sesión en otro recurso de Grape - Sesión).

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

Me doy cuenta de que presento aquí un montón de código y puede que no tenga sentido, pero esto es lo que tengo actualmente y puedo usar el authentication_token para las llamadas en mi API que están protegidas por la Authentication módulo.

Siento que esta solución no es buena, pero realmente busco la forma más fácil de lograr la autenticación de usuario a través de API. Tengo varias preguntas que enumero a continuación.

Preguntas

  1. ¿Crees que este tipo de implementación es peligrosa, de ser así, por qué? - Creo que lo es, debido al uso de un token. ¿Hay alguna manera de mejorar este patrón? También he visto la implementación con Token modelo separado que tiene tiempo de vencimiento, etc. Pero creo que esto es casi como reinventar la rueda, porque para este propósito puedo implementar OAuth2. Me gustaría tener una solución más ligera.
  2. Es una buena práctica crear un nuevo módulo para Autenticación e incluirlo solo en los recursos donde sea necesario.
  3. ¿Conoces algún buen tutorial sobre este tema: implementar Rails + Devise + Grape? Además, ¿conoces algún buen proyecto de código abierto de Rails, que se implementa de esta manera?
  4. ¿Cómo puedo implementarlo con un enfoque diferente que sea más seguro?

Me disculpo por una publicación tan larga, pero espero que más personas tengan el mismo problema y pueda ayudarme a encontrar más respuestas a mis preguntas.


Aunque me gusta la pregunta y la respuesta de @MZaragoza, creo que vale la pena señalar que token_authentical ha sido eliminado de Devise por algún motivo. El uso de los tokens es vulnerable a los ataques de tiempo. Ver también esta publicación y el blog de Devise. Por lo tanto, no he votado a favor la respuesta de @MZaragoza.

En caso de que use su API en combinación con Doorkeeper, podría hacer algo similar, pero en lugar de buscar el authentication_token en la tabla / modelo de usuario, busca el token en la tabla OauthAccessTokens, es decir,

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

Esto es más seguro, porque ese token (es decir, el access_token real) existe solo por una cierta cantidad de tiempo.

Para poder hacer esto, debe tener un modelo de Usuario y un modelo de OauthAccessToken, con:

class User < ActiveRecord::Base has_many :oauth_access_tokens end class OauthAccessToken < ActiveRecord::Base belongs_to :user, foreign_key: ''resource_owner_id'' end

EDITAR: Tenga en cuenta que generalmente no debe incluir access_token en la URL: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16#section-2.3