Rails : registration + facebook

Bonsoir,



Je me suis lancé sur Rails dans le cadre d'une application iOS. Pour cette app, l'user doit se register et se loguer.



Pour le moment j'ai donc une base données avec une table users et les champs classiques (email, password, salt). La registration et le login fonctionnent bien.



J'aimerai intégrer le registration/login Facebook dans l'application. C'est là  que ça devient flou pour moi.

Facebook propose un tutorial pas à  pas qui a l'air bien fait. Mais comment faire niveau Rails (enfin la techno on s'en fou je pense) pour manager la base de donnée avec ce type de login ? (token etc ...)



Comment faire ensuite si l'user décide de vouloir se loguer via la méthode classique et vice versa ?



C'est vraiment flou et mon post tourne vraiment autour du process.



Il me semble que Ali a mis en place ce système pour l'application FoodReporter image/smile.png' class='bbc_emoticon' alt=':)' />

Si tu lis ce post ... image/rolleyes.gif' class='bbc_emoticon' alt='::)' />

Réponses

  • muqaddarmuqaddar Administrateur
    mars 2013 modifié #2
    Je fais pas mal de Rails, mais j'ai jamais mis en place ce genre d'authentification (j'ai un système classique perso).



    Par contre, je te rejoins sur ce point: dans tous les cas (php ou Rails), tu attaqueras toujours des URL...



    Regarde aussi du côté des plugins Rails Facebook:

    https://www.ruby-toolbox.com/search?utf8=✓&q=facebook



    Ou encore ce RailsCast:

    http://railscasts.com/episodes/360-facebook-authentication?view=asciicast



    N'hésite pas si tu as une question plus précise sur Rails.
  • Merci pour ces indications c'est cool !



    Je débute vraiment sur Rails.

    Pour le moment j'ai juste un contrôleur que ma route appelle en fonction de l'URL.

    J'ai donc ça au niveau du contrôleur :


    <br />
    class UserController &lt; ApplicationController<br />
      def create<br />
       @user  = User.new(params[:post])<br />
       result = User.find_by_email(@user.email)<br />
       if result.blank?<br />
        date = Time.now.to_i.to_s<br />
        @user.salt = Digest::SHA1.hexdigest(date)<br />
        @user.password = Digest::SHA1.hexdigest(@user.password+@user.email+@user.salt)<br />
        @user.save<br />
        output = {&#39;status&#39; =&gt; 1}.to_json<br />
        render :json =&gt; output<br />
       else<br />
        output = {&#39;status&#39; =&gt; 0}.to_json<br />
        render :json =&gt; output<br />
       end<br />
      end<br />
      def login<br />
       @user  = User.new(params[:post])<br />
       result = User.find_by_email(@user.email)<br />
       if result.blank?<br />
        output = {&#39;status&#39; =&gt; -1}.to_json<br />
        render :json =&gt; output<br />
       else   <br />
        password = Digest::SHA1.hexdigest(@user.password.to_s+@user.email.to_s+result.salt.to_s)<br />
        if password == result.password<br />
    	  output = {&#39;status&#39; =&gt; 1}.to_json<br />
    	  render :json =&gt; output<br />
        else<br />
    	  output = {&#39;status&#39; =&gt; 0}.to_json<br />
    	  render :json =&gt; output<br />
        end<br />
       end<br />
      end<br />
    end<br />
    




    Je ne sais pas si c'est vraiment la bonne méthode à  adopter ...
  • muqaddarmuqaddar Administrateur
    Le code est plutôt propre.

    Par contre, tu devrais avoir beaucoup plus de code localisé dans ton modèle User que dans le contrôleur.



    Par exemple, tu pourrais avoir une méthode authenticate dans User:


    <br />
    def self.authenticate(username, password)<br />
      //code pour authentifier<br />
    end<br />
    




    Et une méthode privée pour encrypter le pass dans User:




    <br />
    private <br />
    def encrypt_password<br />
       if password.present?<br />
         self.password_salt = BCrypt::Engine.generate_salt<br />
         self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)<br />
       end<br />
    end
    




    (j'utilise bcrypt et non Digest mais chacun sa techno)



    Voilà , pour résumer, pense au MVC !

    (pour ma part, c'est Rails qui m'a le plus fait comprendre le MVC)
  • Merci pour ces indications. Oui pour le MVC faut juste que je vois comment tout ça se câble image/smile.png' class='bbc_emoticon' alt=':)' />

    Pour Digest j'ai trouvé ça sur le net. Je regarderai bcrypt.



    Thx !
  • Dans ton model et la methode de check, tu as le droit d'appeler @user ... find_by_username(username) ?
  • muqaddarmuqaddar Administrateur
    mars 2013 modifié #7
    Oui.

    En gros , t'aurais ça:




    <br />
    def self.authenticate(username, password)<br />
      user = User.find_by_username(username)<br />
      return user if user and user.status == 1 and user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)<br />
      return nil<br />
    end
    
  • En effet ça marche super image/smile.png' class='bbc_emoticon' alt=':)' />
  • Pour reprendre mon problème de db design je pense partir sur trois table :
    • facebook_users
    • standard_users
    • users


    Les deux premières ayant des id uniques même entres-elles et linkés sur la dernière qui, elle, regroupe toutes les informations communes à  chaque user.

    Je ne sais pas si c'est une bonne idée. J'avais pensé partir sur une table users unique avec des champs ne servant qu'aux users facebook et d'autres ne servant qu'aux standard users. Certains seraient donc vide selon le type adopté.



    Vous avez un avis ?
  • muqaddarmuqaddar Administrateur
    avril 2013 modifié #10
    Moi je pense qu'une seule table users, ça suffit amplement.

    Avec 2 méthodes:

    - authenticate(login, pass)

    - authenticate_with_facebook_account(login, pass)



    Après, comme je le disais, je ne sais pas du tout comment marche l'authentification facebook.

    Je suppose qu'on envoie le login/pass aux API facebook et que FB renvoie les infos d'identification, du coup, tu peux ouvrir une session Rails avec les infos de la table. Je n'ai pas regardé le RailsCast sur FB.
  • Oui je pense partir sur une table unique en effet.

    Pour l'auth facebook en gros là  j'arrive à  me log avec leur SDK et à  récupérer l'ID de l'user fb associé. Du coup la première fois que l'user se log je chope l'id que j'envoie en base chez moi puis je l'utilise par la suite pour récupérer ses infos image/smile.png' class='bbc_emoticon' alt=':)' />

    Je pense aussi envoyer à  chaque requête une clef secrète (un hash de mon CFBundleVersion par ex) pour sécuriser un peu plus la requête.
  • Plus j'avance plus je me demande si passer par un service de cloud ne serait pas mieux pour tout gérer.

    J'ai des images que j'aimerai sans doute pouvoir envoyer sur le clous si je veux faire une app iPad et même pour d'autres plateforme. Et si l'user delete l'app il pourra récupérer son compte avec vraiment toutes les images. Je suis tiraillé ...

    Parse a l'air cool (1 million de call API et 1Go de stockage). Ou sinon je mets en place mon propre système de cloud.
  • muqaddarmuqaddar Administrateur
    'Ceetix' a écrit:


    Oui je pense partir sur une table unique en effet.

    Pour l'auth facebook en gros là  j'arrive à  me log avec leur SDK et à  récupérer l'ID de l'user fb associé. Du coup la première fois que l'user se log je chope l'id que j'envoie en base chez moi puis je l'utilise par la suite pour récupérer ses infos image/smile.png' class='bbc_emoticon' alt=':)' />

    Je pense aussi envoyer à  chaque requête une clef secrète (un hash de mon CFBundleVersion par ex) pour sécuriser un peu plus la requête.




    Rails fabrique un token par défaut que tu peux envoyer et récupérer à  chaque requête.
  • muqaddarmuqaddar Administrateur
    avril 2013 modifié #14
    'Ceetix' a écrit:


    Plus j'avance plus je me demande si passer par un service de cloud ne serait pas mieux pour tout gérer.

    J'ai des images que j'aimerai sans doute pouvoir envoyer sur le clous si je veux faire une app iPad et même pour d'autres plateforme. Et si l'user delete l'app il pourra récupérer son compte avec vraiment toutes les images. Je suis tiraillé ...

    Parse a l'air cool (1 million de call API et 1Go de stockage). Ou sinon je mets en place mon propre système de cloud.




    J'utilise le gem Paperclip pour les images, et j'envoie tout sur Amazon S3.

    L'avantage d'Amazon S3, c'est le coût...

    Je gère les compte users sur mon serveurs.



    En gros, ça donne ça dans le modèle et basta, rien d'autre à  faire:


    <br />
      has_attached_file :image,<br />
    					:styles =&gt; { :tiny =&gt; &quot;70x70&gt;&quot;, :small =&gt; &quot;160x240&quot;, :high =&gt; &quot;640x960&quot; },<br />
    					:path =&gt; &quot;/upload/:class/:attachment/:exploded_id/:style/:basename.:extension&quot;,<br />
    					:url  =&gt; &quot;:s3_eu_url&quot;,<br />
    					:storage =&gt; :s3,<br />
    					:s3_credentials =&gt; &quot;config/s3.yml&quot;<br />
    <br />
    <br />
    




    Sinon, je compte aussi essayer Parse 1 de ses 4.
  • Tu peux expliquer un peu la structure de ton entité path ?

    Oui et le cauchemar je veux l'éviter. Donc du coup sans doute une synchro Helios / Parse juste la base de donnée, puis un Amazon S3 pour les media car en effet ça coute rien du tout.
  • muqaddarmuqaddar Administrateur
    [color=#666600][font=monospace][size=3]:[/size][/font][/color][color=#000000][font=monospace][size=3]path [/size][/font][/color][color=#666600][font=monospace][size=3]=&gt;[/size][/font][/color][color=#000000][font=monospace][size=3] [/size][/font][/color][color=#008800][font=monospace][size=3]&quot;/upload/:class/:attachment/:exploded_id/:style/:basename.:extension&quot;[/size][/font][/color][color=#666600][font=monospace][size=3],[/size][/font][/color]
    




    - upload c'est le nom du dossier que tu appelles comme tu veux (il se trouvera dans /public)

    - :class, c'est le nom du modèle

    - :attachement c'est le nom du champ dans la table qui stocke le nom du fichier

    - oublie exploded_id (c'est un truc perso), normalement, il y a :id, l'id du record

    - :style, c'est "thumb", "full", "medium"... si tu décides de faire plusieurs versions de l'image

    - :basename est le nom du fichier avec l'extension



    Bon, enfin, tu gères ça comme tu veux.

    Soit en local sur ton serveur, soit tu envoies tout sur Amazon.
Connectez-vous ou Inscrivez-vous pour répondre.