Cette API d’authentification Rails JSON (en utilisant Devise) est-elle sécurisée?

Mon application Rails utilise Devise pour l’authentification. Il possède une application iOS soeur et les utilisateurs peuvent se connecter à l’application iOS en utilisant les mêmes informations d’identification que celles utilisées pour l’application Web. J’ai donc besoin d’une sorte d’API pour l’authentification.

Beaucoup de questions similaires sur ce point pointent vers ce tutoriel , mais il semble être obsolète, puisque le module token_authenticatable a depuis été supprimé de Devise et que certaines des lignes jettent des erreurs. (J’utilise Devise 3.2.2.) J’ai essayé de rouler moi-même en me basant sur ce tutoriel (et celui-ci ), mais je ne suis pas sûr à 100% – j’ai l’impression qu’il y a quelque chose que j’ai mal compris ou manqué.

Tout d’abord, en suivant les conseils de cet aperçu , j’ai ajouté un atsortingbut de texte authentication_token à ma table d’ users , et ce qui suit à 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 

J’ai ensuite les contrôleurs suivants:

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 

(Notez que mon application_controller a la ligne before_filter :authenticate_user! )

api / sessions_controller.rb

 class Api::SessionsController  [: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 

Et dans config / routes.rb :

  namespace :api, defaults: { format: "json" } do devise_for :users end 

Je suis un peu hors de ma profondeur et je suis sûr qu’il y a quelque chose ici que mon futur moi reverra (il y en a habituellement). Quelques parties difficiles:

Tout d’abord , vous remarquerez Api::SessionsController hérite de Devise::RegistrationsController alors Api::RegistrationsController hérite d’ ApiController (j’ai aussi d’autres contrôleurs tels que Api::EventsController < ApiController qui traitent plus de choses REST standard pour mon autre modèles et n’ont pas beaucoup de contact avec Devise.) C’est un arrangement assez moche, mais je ne pouvais pas trouver un autre moyen d’accéder aux méthodes dont j’ai besoin dans Api::RegistrationsController . Le tutoriel que j’ai lié ci-dessus a la ligne include Devise::Controllers::InternalHelpers , mais ce module semble avoir été supprimé dans les versions plus récentes de Devise.

Deuxièmement , j’ai désactivé la protection CSRF avec la ligne skip_before_filter :verify_authentication_token . J’ai des doutes quant à savoir si c’est une bonne idée – je vois beaucoup de conseils contradictoires ou difficiles à comprendre sur la vulnérabilité des API JSON aux attaques CSRF – mais l’ajout de cette ligne était le seul moyen de faire fonctionner cette fichue chose.

Troisièmement , je veux m’assurer de comprendre comment l’authentification fonctionne une fois qu’un utilisateur s’est connecté. Disons que j’ai un appel d’API GET /api/friends qui renvoie une liste des amis de l’utilisateur actuel. Si je comprends bien, l’application iOS devrait obtenir l’ authentication_token de l’utilisateur de la firebase database (qui est une valeur fixe pour chaque utilisateur qui ne change jamais ??), puis l’envoyer en tant que paramètre avec chaque demande, par exemple GET /api/friends?authentication_token=abcdefgh1234 , alors mon Api::FriendsController pourrait faire quelque chose comme User.find_by(authentication_token: params[:authentication_token]) pour obtenir le current_user. Est-ce vraiment si simple ou quelque chose me manque-t-il?

Donc, pour ceux qui ont réussi à lire tout le chemin jusqu’à la fin de cette question gigantesque, merci pour votre temps! Pour résumer:

  1. Ce système de connexion est-il sécurisé? Ou y a-t-il quelque chose que j’ai oublié ou mal compris, par exemple en ce qui concerne les attaques CSRF?
  2. Est-ce que ma compréhension de la façon d’authentifier les demandes une fois que les utilisateurs sont connectés est correcte? (Voir “troisièmement …” ci-dessus.)
  3. Est-ce que ce code peut être nettoyé ou amélioré? En particulier, la mauvaise conception d’un contrôleur hérite de Devise::RegistrationsController et des autres ApiController d’ ApiController .

Merci!

Vous ne voulez pas désactiver CSRF, j’ai lu que les gens pensaient que cela ne s’appliquait pas aux API JSON pour une raison quelconque, mais il s’agit d’un malentendu. Pour le garder activé, vous souhaitez apporter quelques modifications:

  • côté serveur append un after_filter à votre contrôleur de sessions:

     after_filter :set_csrf_header, only: [:new, :create] protected def set_csrf_header response.headers['X-CSRF-Token'] = form_authenticity_token end 

    Cela va générer un jeton, le placer dans votre session et le copier dans l’en-tête de réponse pour les actions sélectionnées.

  • côté client (iOS), vous devez vous assurer que deux choses sont en place.

    • Votre client doit parsingr toutes les réponses du serveur pour cet en-tête et le conserver lorsqu’il est transmis.

       ... 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 NSSsortingng *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"]; if (token) [yourConfig setCsrfToken:token]; 
    • Enfin, votre client doit append ce jeton à toutes les requêtes ‘non GET’ qu’il envoie:

       ... get ahold of your request object if (yourConfig.csrfToken && ![request.httpMethod isEqualToSsortingng:@"GET"]) [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"]; 

La dernière pièce du puzzle est de comprendre que lors de la connexion, deux sessions / jetons CSRF sont utilisés. Un stream de connexion ressemblerait à ceci:

 GET /users/sign_in -> // new action is called, initial token is set // now send login form on callback: POST /users/sign_in  -> // create action called, token is reset // when login is successful, session and token are replaced // and you can send authenticated requests 

Votre exemple semble imiter le code du blog Devise – https://gist.github.com/josevalim/fb706b1e933ef01e4fb6

Comme mentionné dans cet article, vous le faites de la même façon que l’option 1, qui est l’option précaire. Je pense que la clé est que vous ne voulez pas simplement réinitialiser le jeton d’authentification chaque fois que l’utilisateur est enregistré. Je pense que le jeton devrait être créé explicitement (par une sorte de TokenController dans l’API) et devrait expirer périodiquement.

Vous remarquerez que je dis “je pense” car (pour autant que je sache) personne n’a plus d’informations à ce sujet.

Les 10 vulnérabilités les plus courantes dans les applications Web sont documentées dans le Top 10 OWASP . Cette question mentionnait que la protection CSRF (Cross-Site Request Forgery) était désactivée et que CSRF se trouvait dans le Top 10 OWASDP . En résumé, les attaquants utilisent CSRF pour effectuer des actions en tant qu’utilisateur authentifié. La désactivation de la protection CSRF entraînera des vulnérabilités à haut risque dans une application et compromettra l’objective de disposer d’un système d’authentification sécurisé. Il est probable que la protection CSRF échoue car le client ne parvient pas à transmettre le jeton de synchronisation CSRF.

Lisez l’intégralité du top 10 OWASP, à défaut de le faire est extrêmement dangereux . Portez une attention particulière à l’ authentification brisée et à la gestion de session , consultez également le Cheat Sheet de gestion de session .