¿Es segura esta API de autenticación JSON de Rails (usando Devise)?
Mi aplicación 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 usando las mismas credenciales que usan para la aplicación web. Así que necesito algún tipo de API para la autenticación.
Muchas preguntas similares aquí apuntan a este tutorial, pero parece estar desactualizado, ya que el módulo token_authenticatable
ha sido eliminado de Devise y algunas de las líneas arrojan errores. (Estoy usando Devise 3.2.2.) He intentado rodar mi propio basado en eso tutorial (y este), pero no estoy 100% seguro en él - Siento que puede haber algo que he malinterpretado o perdido.
En primer lugar, siguiendo el consejo de este gist, agregué un atributo de texto authentication_token
a mi tabla users
, y lo siguiente a user.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
Entonces 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 mi application_controller
tiene la línea before_filter :authenticate_user!
.)
Api / sessions_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 en config/routes.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 yo futuro mirará hacia atrás y se encogerá (generalmente lo hay). Algunas partes dudosas:
En primer lugar , notarás que Api::SessionsController
hereda de Devise::RegistrationsController
mientras que Api::RegistrationsController
hereda de ApiController
(también tengo algunos otros controladores como Api::EventsController < ApiController
que se ocupan de cosas REST más estándar para mis otros modelos y no tienen mucho contacto con Devise.) Este es un arreglo bastante feo, pero no pude encontrar otra manera de obtener acceso a los métodos que necesito en Api::RegistrationsController
. El tutorial al que enlacé anteriormente 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 desactivado la protección CSRF con la línea skip_before_filter :verify_authentication_token
. Tengo mis dudas sobre si esta es una buena idea-Veo un montón de conflictivos o difíciles de entender consejos sobre si las API JSON son vulnerables a los ataques CSRF - pero agregar esa línea fue la única manera en que pude hacer que la maldita cosa funcionara.
En tercer lugar , quiero asegurarme de que entiendo cómo funciona la autenticación una vez que un usuario ha iniciado sesión. Digamos que tengo una llamada API GET /api/friends
que devuelve una lista de los amigos del usuario actual. Según lo entiendo, la aplicación iOS tendría que obtener el authentication_token
de la base de datos (¿cuál es un valor fijo para cada usuario que nunca cambia??), luego enviarlo como un param junto con cada solicitud, por ejemplo GET /api/friends?authentication_token=abcdefgh1234
, entonces mi Api::FriendsController
podría hacer algo como User.find_by(authentication_token: params[:authentication_token])
para obtener el current_user. Es realmente así de simple, o me estoy perdiendo algo?
Así que para cualquiera que haya logrado leer todo el camino hasta el final de esta pregunta gigantesca, ¡gracias por su tiempo! Para resumir:
- ¿Es seguro este sistema de inicio de sesión? O hay algo que he pasado por alto o mal entendido, por ejemplo, cuando se trata de ataques CSRF?
- ¿Entiendo correctamente cómo autenticar las solicitudes una vez que los usuarios han iniciado sesión? (Ver "en tercer lugar..." arriba.)
-
¿Hay alguna manera de que este código se pueda limpiar o hacer más bonito? Particularmente el feo diseño de tener un controlador heredado de
Devise::RegistrationsController
y los otros deApiController
.
Gracias!
3 answers
No desea deshabilitar CSRF, he leído que la gente piensa que no se aplica a las API JSON por alguna razón, pero esto es un malentendido. Para mantenerlo habilitado, desea realizar algunos cambios:
-
En el lado del servidor agregue un after_filter a su controlador de sesiones:
after_filter :set_csrf_header, only: [:new, :create] protected def set_csrf_header response.headers['X-CSRF-Token'] = form_authenticity_token end
Esto generará un token, lo pondrá en su sesión y lo copiará en el encabezado de respuesta para las acciones seleccionadas.
-
Lado del cliente (iOS) es necesario asegurarse de que dos cosas son en su lugar.
-
Su cliente necesita escanear todas las respuestas del servidor para este encabezado y conservarlo cuando se pase.
... get ahold of response object // response may be a NSURLResponse object, so convert: NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; // grab token if present, make sure you have a config object to store it in NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"]; if (token) [yourConfig setCsrfToken:token];
-
Finalmente, su cliente necesita agregar este token a todas las solicitudes 'no GET' que envía:
... get ahold of your request object if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"]) [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
-
La pieza final del rompecabezas es entender que al iniciar sesión en devise, se están utilizando dos tokens sessions/csrf posteriores. Un flujo de inicio de sesión se vería así:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-29 18:38:05
Tu ejemplo parece imitar el código del blog de Devise - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
Como se mencionó en ese post, lo estás haciendo de manera similar a la opción 1, que dicen que es la opción insegura. Creo que la clave es que no desea simplemente restablecer el token de autenticación cada vez que se guarda el usuario. Creo que el token debe ser creado explícitamente (por algún tipo de TokenController en la API) y debe expirar periódica.
Te darás cuenta de que digo 'creo' ya que (por lo que puedo decir) nadie tiene más información sobre esto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-10 01:46:33
Las 10 vulnerabilidades más comunes en aplicaciones web están documentadas en el OWASP Top 10. Esta pregunta mencionó que la protección contra la Falsificación de Solicitudes entre sitios(CSRF) estaba deshabilitada, y CSRF está en el Top 10 de OWASDP. En resumen, CSRF es utilizado por los atacantes para realizar acciones como un usuario autenticado. Deshabilitar la protección CSRF conducirá a vulnerabilidades de alto riesgo en una aplicación y socavará el propósito de tener un sistema de autenticación seguro. Es probable que el La protección CSRF estaba fallando, porque el cliente no pasa el token de sincronización CSRF.
Leer el top 10 completo de OWASP, no hacerlo es extremadamente peligroso. Preste mucha atención a Autenticación rota y Administración de sesiones, también consulte la Hoja de trucos de Administración de sesiones .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-28 01:45:36