ruby on rails - tokens - ¿Es segura la API de autenticación Rails JSON(usando Devise)?
rails authentication (3)
Las 10 vulnerabilidades más comunes en aplicaciones web están documentadas en el Top 10 de OWASP . Esta pregunta mencionaba que la protección contra falsificación de solicitudes entre sitios (CSRF) estaba deshabilitada y CSRF está en el Top 10 de OWASDP . En resumen, los atacantes utilizan CSRF para realizar acciones como usuarios autenticados. La desactivación de la protección CSRF generará vulnerabilidades de alto riesgo en una aplicación y socavará el objetivo de tener un sistema de autenticación seguro. Es probable que la protección CSRF esté fallando, porque el cliente no pasa el token de sincronización CSRF.
Lea todo el TOP 10 de OWASP, no hacerlo es extremadamente peligroso . Preste mucha atención a Broken Authentication y Session Management , también consulte la Cheat Sheet de Session Management .
La aplicación My Rails usa Devise para la autenticación. Tiene una aplicación hermana de iOS, y los usuarios pueden iniciar sesión en la aplicación iOS usando las mismas credenciales que usan para la aplicación web. Entonces necesito algún tipo de API para la autenticación.
Muchas preguntas similares apuntan a este tutorial , pero parece estar desactualizado, ya que el módulo token_authenticatable
ya se ha eliminado de Devise y algunas de las líneas arrojan errores. (Estoy usando Devise 3.2.2.) He intentado hacer mi propio modelo basado en ese tutorial (y este ), pero no estoy 100% seguro de ello. Siento que puede haber algo que tengo malentendido o perdido.
En primer lugar, siguiendo el consejo de esta idea general , agregué un atributo de texto authentication_token
a mi tabla de users
y el 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
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 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 un poco fuera de mi profundidad y estoy seguro de que hay algo aquí que mi futuro mirará hacia atrás y se encogerá (por lo general lo hay). Algunas partes dudosas:
En primer lugar , notará 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 tratan con más cosas REST estándar para mi otro 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 he vinculado anteriormente tiene la línea que include Devise::Controllers::InternalHelpers
, pero este módulo parece haberse eliminado en las 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 muchos consejos conflicting o difíciles de entender sobre si las API JSON son vulnerables a los ataques CSRF, pero agregar esa línea era la única forma en que podía hacer que 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 tengo entendido, la aplicación iOS debería obtener la authentication_token
del usuario de la base de datos (que es un valor fijo para cada usuario que nunca cambia?), Luego enviarla como un parámetro 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 usuario_actual. ¿Es realmente así de simple o me falta algo?
Entonces, para cualquiera que haya podido leer todo el camino hasta el final de esta gran pregunta, ¡gracias por su tiempo! Resumir:
- ¿Este sistema de inicio de sesión es seguro? ¿O hay algo que he pasado por alto o malinterpretado, por ejemplo, cuando se trata de ataques de CSRF?
- ¿Tengo conocimiento de cómo autenticar las solicitudes una vez que los usuarios inicien sesión correctamente? (Ver "en tercer lugar ..." arriba).
- ¿Hay alguna manera de que este código se pueda limpiar o hacer más agradable? Particularmente el desagradable diseño de tener un controlador heredado de
Devise::RegistrationsController
y los otros deApiController
.
¡Gracias!
No desea deshabilitar CSRF, he leído que las personas piensan que no se aplica a las API JSON por algún motivo, pero esto es un malentendido. Para mantenerlo habilitado, desea realizar algunos cambios:
allí el lado del servidor agrega 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.
del lado del cliente (iOS) necesita asegurarse de que haya dos cosas en su lugar.
su cliente necesita escanear todas las respuestas del servidor para este encabezado y retenerlo cuando se transfiere.
... 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 comprender que al iniciar sesión para diseñar, se utilizan dos tokens subsiguientes de sesiones / csrf. 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
Tu ejemplo parece imitar el código del blog Devise - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
Como se menciona en esa publicación, lo está 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 crearse explícitamente (mediante algún tipo de TokenController en la API) y debe caducar periódicamente.
Notarás que digo ''Creo'' ya que (por lo que puedo ver) nadie tiene más información sobre esto.