¿Es segura esta API de autenticación Rails JSON (usando Devise)?
La aplicación My Rails utiliza Devise para la autenticación. Tiene una aplicación hermana de iOS y los usuarios pueden iniciar sesión en la aplicación de iOS con las mismas credenciales que utilizan para la aplicación web. Así que necesito algún tipo de API para la autenticación.
Muchas preguntas similares aquí apuntan aeste tutorial, pero parece estar desactualizado, ya que eltoken_authenticatable
desde entonces, el módulo se ha eliminado de Devise y algunas de las líneas arrojan errores. (Estoy usando Devise 3.2.2.) Intenté rodar mi propio basado en ese tutorial (yéste), pero no estoy 100% seguro de ello. Siento que puede haber algo que no he entendido o que no he entendido.
En primer lugar, siguiendo el consejo deesta esencia, Agregué unauthentication_token
atributo de texto a miusers
mesa, y lo siguiente auser.rb
:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Luego tengo los siguientes controladores:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Tenga en cuenta que miapplication_controller
tiene la líneabefore_filter :authenticate_user!
.)
api / session_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :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
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api / registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
Y enconfig / route.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Estoy fuera de mi profundidad un poco y estoy seguro de que hay algo aquí que mi futuro yo volverá a ver y temblará (generalmente hay). Algunas partes dudosas:
en primer lugar, te darás cuenta de queApi::SessionsController
hereda deDevise::RegistrationsController
mientrasApi::RegistrationsController
hereda deApiController
(También tengo algunos otros controladores comoApi::EventsController < ApiController
que tienen que ver con el material REST más estándar para mis otros modelos y no tengo mucho contacto con Devise.) Este es un arreglo bastante feo, pero no pude encontrar otra forma de acceder a los métodos que necesito enApi::RegistrationsController
. El tutorial al que he vinculado arriba tiene la línea.include Devise::Controllers::InternalHelpers
, pero este módulo parece haber sido eliminado en versiones más recientes de Devise.
En segundo lugar, He deshabilitado la protección CSRF con la líneaskip_before_filter :verify_authentication_token
. Tengo mis dudas sobre si esto es una buena idea, veo muchascontradictorio odifícil de entender consejos sobre si las API de JSON son vulnerables a los ataques CSRF, pero agregar esa línea era la única forma en que podía hacer que funcionara la maldita cosa.
En tercer lugar, Quiero asegurarme de que entiendo cómo funciona la autenticación una vez que un usuario ha iniciado sesión. Supongamos que tengo una llamada a la APIGET /api/friends
que devuelve una lista de los amigos del usuario actual. Según tengo entendido, la aplicación iOS tendría que obtener el usuarioauthentication_token
de la base de datos (que es un valor fijo para cada usuario que nunca cambia?), luego envíelo como parámetro junto con cada solicitud, por ejemplo.GET /api/friends?authentication_token=abcdefgh1234
, entonces miApi::FriendsController
podría hacer algo comoUser.find_by(authentication_token: params[:authentication_token])
para obtener el current_user. ¿Es realmente tan simple, o me estoy perdiendo algo?
Así que, para cualquiera que haya logrado leer hasta el final de esta pregunta gigantesca, ¡gracias por su tiempo! Resumir:
¿Es seguro este sistema de inicio de sesión? ¿O hay algo que he pasado por alto o que no he entendido bien, por ejemplo? Cuando se trata de ataques CSRF?¿Mi comprensión de cómo autenticar las solicitudes una vez que los usuarios han iniciado sesión es correcta? (Vea "en tercer lugar ..." arriba).¿Hay alguna forma en que este código pueda ser limpiado o mejorado? Particularmente el diseño feo de tener un controlador heredado deDevise::RegistrationsController
y los otros deApiController
.¡Gracias!