web-dev-qa-db-fra.com

Authentifier un utilisateur à l'aide d'omniauth et de Facebook pour une API Rails?

Je construis une API Rails et ai réussi à créer un moyen pour un utilisateur de s'authentifier à l'aide d'Omniauth Identity.

Nous publions simplement sur auth/identity/callback depuis le client, en lui passant un auth_key et un mot de passe.
Le serveur renvoie ensuite un jeton gardien qui utilise ensuite l'utilisateur pour accéder à l'application et s'identifier.

Ce diagramme illustre ceci:

Client server relationship

Nous aimerions maintenant implémenter une connexion Facebook à partir du client, mais nous avons du mal à la faire fonctionner, à la fois théoriquement et concrètement.

Sur une application Rails simple avec Omniauth Identity, vous appelez simplement auth/facebook, mais si nous mettons un lien à partir de cela dans le client, le serveur est appelé et le serveur enregistre ensuite:

INFO -- omniauth: (facebook) Request phase initiated.

L'application est correctement configurée dans Facebook avec un identifiant et un secret. Vous pouvez donc peut-être renvoyer l'invite de connexion au serveur?

Je suis confus en enchaînant l'authentification. Toute aide appréciée avec reconnaissance!

enter image description here

45
idrysdale

la meilleure façon que j'ai trouvée (après avoir été bloqué pendant un moment sur cette question) est de faire votre omniauth2 (spécifiquement dans mon cas en utilisant satellizer plugin angulaire) manuellement ...

Je vais discuter de la solution pour Facebook, comme c'était mon cas, mais tout pourrait s'appliquer à n'importe quel autre fournisseur.

vous devez d’abord savoir comment fonctionne omniauth2 ( comme documenté pour les humains ici ) ...

  1. Client: Ouvre une fenêtre contextuelle permettant à l'utilisateur de s'authentifier.
  2. Client: Connectez-vous (si nécessaire), puis autorisez l'application.
  3. Client: Après autorisation, la fenêtre contextuelle est redirigée vers votre application. avec le paramètre de chaîne de requête code(code d'autorisation)

l'URL de redirection en arrière doit correspondre à l'URL de votre application frontale et non à l'URL du site principal; elle doit être spécifiée dans les configurations de votre application facebook

  1. Client: Le paramètre code est renvoyé à la fenêtre parente qui a ouvert la fenêtre contextuelle.
  2. Client: La fenêtre parent ferme la fenêtre contextuelle et envoie une demande POST à backend/auth/facebook avec le paramètre code.
  3. Serveur:code (Le code d'autorisation) est échangé contre access token

ici est décrit en détail comment échanger la code contre un access-token depuis documentation pour les développeurs facebook

  1. Serveur: utilisez le access-token récupéré à l'étape 6 pour récupérer les informations de l'utilisateur.

  2. VOILA vous avez vous-même un utilisateur que vous pouvez fusionner/créer un compte/lien avec d'autres fournisseurs oauth/etc. mais gardez à l'esprit que l'utilisateur peut révoquer certaines autorisations (comme email, facebook prend en charge la révocation de certaines autorisations) ...


(assez parlé, montre-moi du code)

Vous devez d’abord ajouter HTTParty gem à votre Gemfile

gem 'httparty'  # Makes http fun again (http client)

J'ai ajouté this Gist qui contient le déroulement de l'étape (6, 7 et 8). Ces étapes sont les plus problématiques et ne sont documentées presque nulle part.

gist exporte 2 méthodes principales:

Omniauth::Facebook.authenticate(authorization_code)

qui est utilisé pour authentifier l'utilisateur avec facebook et renvoyer le user_info, long_live_access_token (valable pendant 60 jours)

Omniauth::Facebook.deauthorize(access_token)

qui est utilisé pour désautoriser/révoquer les access_token et les autorisations d'application sur facebook ...

Ceci est utilisé pour l'exigence spéciale que j'ai, lorsque l'utilisateur révoque l'autorisation d'e-mail demandée sur la connexion facebook ... nous révoquons toutes les autorisations d'application ... ceci invitera l'utilisateur à la prochaine connexion, comme s'il s'agissait de sa première connexion (inutile pour accéder aux applications facebook et révoquer manuellement l'application) ...

voici comment il est utilisé dans le contrôleur

user_info, access_token = Omniauth::Facebook.authenticate(params['code'])
if user_info['email'].blank?
  Omniauth::Facebook.deauthorize(access_token)
end

C'est ça ... maintenant si vous êtes intéressé par les aspects internes de la mise en oeuvre ... voici le code tel qu'il est vu dans le Gist. (ajouté pour référence) N'hésitez pas à le bifurquer, le modifier, aider à le rendre meilleur.

require 'httparty'

module Omniauth
  class Facebook
    include HTTParty

    # The base uri for facebook graph API
    base_uri 'https://graph.facebook.com/v2.3'

    # Used to authenticate app with facebook user
    # Usage
    #   Omniauth::Facebook.authenticate('authorization_code')
    # Flow
    #   Retrieve access_token from authorization_code
    #   Retrieve User_Info hash from access_token
    def self.authenticate(code)
      provider = self.new
      access_token = provider.get_access_token(code)
      user_info    = provider.get_user_profile(access_token)
      return user_info, access_token
    end

    # Used to revoke the application permissions and login if a user
    # revoked some of the mandatory permissions required by the application
    # like the email
    # Usage
    #    Omniauth::Facebook.deauthorize(access_token)
    # Flow
    #   Send DELETE /me/permissions?access_token=XXX
    def self.deauthorize(access_token)
      options  = { query: { access_token: access_token } }
      response = self.delete('/me/permissions', options)

      # Something went wrong most propably beacuse of the connection.
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.deauthorize Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.deauthorization'
      end
      response.parsed_response
    end

    def get_access_token(code)
      response = self.class.get('/oauth/access_token', query(code))

      # Something went wrong either wrong configuration or connection
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.get_access_token Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.access_token'
      end
      response.parsed_response['access_token']
    end

    def get_user_profile(access_token)
      options = { query: { access_token: access_token } }
      response = self.class.get('/me', options)

      # Something went wrong most propably beacuse of the connection.
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.get_user_profile Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.user_profile'
      end
      response.parsed_response
    end


    private

    # access_token required params
    # https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.3#confirm
    def query(code)
      {
        query: {
          # The authorization_code we want to exchange for the access_token
          code: code,
          # This must match the redirectUrl registerd in the facebook app.
          # You can save it to ENV['WEB_APP_URL'] if you have multiple facebook apps for development and testing
          # so you can support testing app on development and production app on production env.
          redirect_uri: "http://localhost:9000/",
          client_id: ENV['FB_APP_ID'], # Facebook appId
          client_secret: ENV['FB_APP_SECRET'], # Facebook app secret (must not exist on front-end app for security)
        }
      }
    end
  end
end

voici un autre tutoriel sur les nœuds implémentant oauth pour instagram qui m'a permis de comprendre le fonctionnement de oauth2 (ajouté pour référence)

47
a14m

Pour résoudre ce problème, la meilleure ressource que j'ai trouvée est l'application exemple Rails dans le dépôt satellite de github: https://github.com/sahat/satellizer/tree/master/examples/server/Ruby

Votre code de satellite appelle la méthode AuthController.authenticate . Cette méthode utilise les classes de modèle oauth pour chaque fournisseur pour convertir le code que vous recevez en un jeton d'accès. Ensuite, dans votre classe d'utilisateurs vous pouvez récupérer l'utilisateur qui correspond aux informations que vous avez obtenues du fournisseur.

A la fin, la méthode du contrôleur renvoie le jeton jwt au client.

Dans mon cas, la partie contrôleur est un peu différente, car j’utilise aussi le système d’authentification par mail/mot de passe, mais je copie les classes telles quelles et cela fonctionne à merveille.

1
Seb Cesbron

Pour communiquer avec facebook api. Je recommande d'utiliser 'omniauth-facebook' gem . Vous pouvez cloner cet exemple pour en comprendre plus: https://github.com/ralphos/omniauth-facebook-example

0
khanh