tutorial tokens rails hacer gema como ruby-on-rails json api security devise

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:

  1. ¿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?
  2. ¿Tengo conocimiento de cómo autenticar las solicitudes una vez que los usuarios inicien sesión correctamente? (Ver "en tercer lugar ..." arriba).
  3. ¿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 de ApiController .

¡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.