70 votes

Est-ce les Rails JSON l'authentification de l'API (à l'aide de Concevoir) - il sécurisé?

Mon application Rails utilise Concevoir pour l'authentification. Il a une sœur de l'application iOS, et les utilisateurs peuvent se connecter à l'application iOS en utilisant les mêmes informations d'identification qu'ils utilisent pour l'application web. J'ai donc besoin d'une sorte d'API pour l'authentification.

Beaucoup de questions similaires sur ici point de ce tutoriel, mais il semble être out-of-date, comme l' token_authenticatable module a depuis été retiré de Concevoir, et certaines des lignes de générer des erreurs. (Je suis l'aide de Concevoir 3.2.2.) J'ai tenté de rouler mes propres basé sur le tutoriel (et ce seul), mais je ne suis pas 100% confiance en elle - je me sens comme il y a peut être quelque chose que j'ai mal compris ou manqués.

Tout d'abord, en suivant les conseils de ce résumé, j'ai ajouté un authentication_token d'attribut de texte pour ma users tableau, et le suivant à l' 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

Puis-je avoir 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

(À noter que mes application_controller a la ligne 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

Et dans config/routes.rb:

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

Je suis hors de ma profondeur un peu et je suis sûr qu'il y a quelque chose ici que mon futur moi allons regarder en arrière sur et grincer des dents (il y a habituellement). Certains douteux parties:

Tout d'abord, vous remarquerez qu' Api::SessionsController hérite Devise::RegistrationsController alors qu' Api::RegistrationsController hérite ApiController (j'ai aussi quelques autres contrôleurs, tels que Api::EventsController < ApiController qui traitent de plus standard RESTE des trucs pour mes autres modèles et n'ont pas beaucoup de contact avec les Concevoir.) C'est un assez moches, mais je ne pouvais pas imaginer une autre façon d'obtenir l'accès aux méthodes j'ai besoin en Api::RegistrationsController. Le tutoriel, 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 Concevoir.

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 contradictoires ou difficiles à comprendre des conseils quant à savoir si JSON Api sont vulnérables à des attaques CSRF, mais l'ajout de cette ligne a été la seule façon que j'ai pu obtenir la fichue chose à travailler.

Troisièmement, je veux m'assurer de comprendre comment fonctionne l'authentification une fois qu'un utilisateur s'est connecté. Dire que j'ai un appel d'API GET /api/friends qui renvoie une liste de l'utilisateur actuel amis. Si je comprends bien, l'application iOS devrait obtenir l'utilisateur authentication_token de la base de données (qui est une valeur fixe pour chaque utilisateur qui ne change jamais??), puis le présenter comme un param avec chaque demande, par exemple, GET /api/friends?authentication_token=abcdefgh1234, puis mon Api::FriendsController pourrait faire quelque chose comme User.find_by(authentication_token: params[:authentication_token]) pour obtenir le current_user. Est-ce vraiment aussi simple, ou ai-je raté quelque chose?

Donc, pour toute personne qui a réussi à lire tout le chemin à la fin de cette grande question, merci pour votre temps! Pour résumer:

  1. Est-ce le système de connexion sécurisé? Ou est-il quelque chose que j'ai oublié ou mal compris, par exemple, lorsqu'il est livré à des attaques CSRF?
  2. Est ma compréhension de la façon d'authentifier les demandes une fois que les utilisateurs sont inscrits dans le bon? (Voir "enfin..." ci-dessus).
  3. Est-il possible que ce code peut être enlevé ou rendu plus agréable? En particulier le design laid d'avoir un contrôleur hérite de l' Devise::RegistrationsController et les autres à partir d' ApiController.

Merci!

58voto

beno1604 Points 371

Vous ne voulez pas désactiver CSRF, j'ai lu que les gens pensent qu'il ne s'applique pas aux JSON Api pour une raison quelconque, mais c'est un malentendu. De le laisser activé, vous voulez faire quelques changements:

  • il y côté serveur ajouter un after_filter pour vos sessions de contrôleur:

    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 mettre dans votre session et de le copier dans l'en-tête de réponse pour les actions sélectionnées.

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

    • votre client a besoin de numériser toutes les réponses du serveur pour cet en-tête et de 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
      NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"];
      if (token)
         [yourConfig setCsrfToken:token];
      
    • enfin, votre client doit ajouter ce jeton à tous les 'non' demandes il envoie:

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

Dernière pièce du puzzle est de comprendre que lors de la connexion à concevoir, deux de ses sessions ultérieures/csrf jetons sont utilisés. Une connexion flux devrait ressembler à ceci:

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

3voto

Jaco Pretorius Points 9630

Votre exemple semble imiter le code de la devise de blog https://gist.github.com/josevalim/fb706b1e933ef01e4fb6

Comme mentionné dans ce post, vous le faites similaire à l'option 1, qui, disent-ils, est l'insécurité de l'option. Je pense que la clé est que vous ne souhaitez pas il suffit de réinitialiser le jeton d'authentification chaque fois que l'utilisateur est enregistré. Je pense que le jeton doit être créé de manière explicite (par une sorte de TokenController dans l'API) et la fin est périodiquement.

Vous remarquerez que je dis "je pense" depuis aussi loin que je peux dire) personne n'en a plus d'informations sur cette.

0voto

Rook Points 34698

Le top 10 des plus communs vulenrablites dans les applications web sont documentées dans le Top 10 OWASP. Cette question a mentionné que le Cross-Site Request Forgery(CSRF) de protection a été désactivé, et CSRF est sur le OWASDP Top 10. En bref, CSRF est utilisé par des attaquants afin d'exécuter des actions qu'un utilisateur authentifié. La désactivation de la protection CSRF conduira à haut risque vulnérabilités dans une application, et sape le but d'avoir un système d'authentification sécurisé. Il est probable que la protection CSRF n'était pas, car le client est en défaut de transmettre au CSRF de la synchronisation jeton.

Lire l'intégralité du top 10 OWASP, à défaut de le faire est extrêmement dangereux. Attention à Cassé l'Authentification et la Gestion de Session, consultez également la Gestion de Session Feuille de Triche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X