How to use http authentication in devise with an optional omniauth token as the authentication token

后端 未结 2 1316
醉梦人生
醉梦人生 2021-01-30 00:45

We have a rails app setup that uses devise & omniauth to allow logging in via facebook authentication. We also have a mobile app that is currently using http authentication

2条回答
  •  遥遥无期
    2021-01-30 01:05

    Just did this, but never saw an end-to-end solution.

    This addresses point 3. 1 & 2 are easily accomplished with devise and documented elsewhere.

    Not too hard to add FB auth to your web app, the instructions are on github for omniauth and omniauth-facebook.

    I believe the following stands alone, without doing the omniauth-facebook integration, if you want to do it that way. This is similar to other approaches out there. My idea was to attempt to use the devise model as closely as I could.

    You'll need the fb_graph gem.

    On the mobile client, you authenticate with FB appropriately and put the returned access token in the header of your http requests. I used the header fb_access_token. Just like basic auth, you'll want to send this over SSL to avoid sniffing of the token. Using the header allows me to interchange basic auth and FB auth without changing the requests, but you could use a parameter if you prefer.

    This solution implements a warden strategy which is based off of the devise Authenticatable warden strategy. The difference here is the fact that this strategy utilizes an HTTP header called fb_access_token containing the facebook access token string that was retrieved using the mobile application.

    Once you know this, the code is pretty straightforward.

    In a file, in the config/initializers directory, add the following. I happened to call mine fb_api_strategy.rb:

    # authentication strategy to support API authentication over the webservice
    # via facebook
    
    require 'devise/strategies/database_authenticatable'
    require 'fb_graph'
    require 'warden'
    
    module Devise
      module Strategies
        class FbMobileDatabaseAuthenticatable < Authenticatable
      def valid?
        # if we have headers with the facebook access key
        !!request.headers["fb_access_token"]
      end
    
      def authenticate!
        token = request.headers["fb_access_token"]
        fbuser = FbGraph::User.me(token)
        fbuser = fbuser.fetch
    
        user = User.find_for_facebook_mobile_client(fbuser.email)
    
        # this either creates a new user for the valid FB account, or attaches
        # this session to an existing user that has the same email as the FB account
    
        if !!user && validate(user) { true }
          user.after_database_authentication
          success!(user)
        elsif !halted? || !user
          fail(:invalid)
            end
          end
        end
      end
    end
    
    Warden::Strategies.add(:fb_database_authenticatable,
                           Devise::Strategies::FbMobileDatabaseAuthenticatable)
    

    In config/initializers, add the following to devise.rb:

      config.warden do |manager|
        manager.default_strategies(:scope => :user).unshift :fb_database_authenticatable
      end
    

    To allow you to either create a user or find an existing user based on the FB email, add the following to your user model:

      def self.find_for_facebook_mobile_client(fb_email)
        if user = User.where(:email => fb_email).first
          user
        else
          User.create!(:email => fb_email, :password => Devise.friendly_token[0,20])
        end
      end
    

    I don't think fb_database_authenticatable is an accurate name, but I'll leave that as an exercise for the reader. Another exercise is caching/storing the FB access token, and perhaps avoiding the RT to FB with each call. You should note that the access token from the the mobile app and the rails app will be different if you do FB authentication on both sides, which I suspect most people will want to do. This probably impacts your caching scheme.

    I think that does it - happy coding.

提交回复
热议问题