Blog Me think, why waste time say lot word, when few word do trick

Rails API and Facebook login (featuring Doorkeeper).

When it comes to add Facebook login to Rails application there’re tons of guides and examples how to do it using Devise and Omniauth gems. That works well when you need to make it work with “classic” Rails application. But if you have Rails API application that powers your iOS/Android application it’s a bit different story.

Note: This post is only about backend part and it assumes that you know how to obtain Facebook access token (it’s described in this doc).

So that’s what we have:

  • Rails API application with protected endpoints that are accessible only by authenticated users (authenticated through Facebook).
  • iOS/Android client that does Facebook authentication and provides us users’s Facebook access token.

For user authentication we will use OAuth 2 protocol. We gonna use Doorkeeper gem to add OAuth 2 authorization support for our Rails API. By default Doorkeeper supports all common grants like Authorization code, Client credentials, Implicit grant, etc. But it doesn’t work for us. All that we going to get from mobile clients is users’s access token from Facebook. Here comes the Assertion grant. Since it’s not supported by Doorkeeper by default we need to use this doorkeeper-grants_assertion extension. That’s how the flow works:

 Relying
 Party                     Client                   Token Service
   |                          |                         |
   |                          |  1) Request Assertion   |
   |                          |------------------------>|
   |                          |                         |
   |                          |  2) Assertion           |
   |                          |<------------------------|
   |    3) Assertion          |                         |
   |<-------------------------|                         |
   |                          |                         |
   |    4) OK or Failure      |                         |
   |------------------------->|                         |
   |                          |                         |
   |                          |                         |

where Relying Party is our Rails API, Client is iOS/Android app, Token Service is Facebook.

Let’s configure Doorkeeper to support that flow. That’s how your config/initializers/doorkeeper.rb might look:

Doorkeeper.configure do
  # Getting resource owner (User) from the Facebook's access token.
  resource_owner_from_assertion do
    Authentication::Facebook.new(params[:assertion]).user!
  end

  # Allows only assertion flow.
  grant_flows %w(assertion)
end

Yeah, that simple. Authentication::Facebook class is just a simple service that fetches the user information using Facebook’s access token and tries to create a new user or return existing one based on user’s Facebook id. It might look like this:

require 'net/http'

module Authentication
  class Facebook
    FACEBOOK_URL = 'https://graph.facebook.com/v3.0/'
    FACEBOOK_USER_FIELDS = 'id,name,first_name,last_name'

    def initialize(access_token)
      @access_token = access_token
    end

    def user!
      return if user_data.blank? || facebook_id.blank?

      User.find_by(facebook_id: facebook_id) || create_user!
    end

    private

    def create_user!
      User.create!(
        facebook_id: facebook_id,
        first_name: first_name,
        last_name: last_name
      )
    end

    def user_data
      @user_data ||= begin
        response = Net::HTTP.get_response(request_uri)
        JSON.parse(response.body)
      end
    end

    def facebook_id
      user_data['id']
    end

    def first_name
      user_data['first_name']
    end

    def last_name
      user_data['last_name']
    end

    def request_uri
      URI.parse("#{FACEBOOK_URL}me?access_token=#{@access_token}&fields=#{FACEBOOK_USER_FIELDS}")
    end
  end
end

That’s basically it. Finally, you can test that everything works by making a request to our Rails API:

curl -X POST \
  http://localhost:3000/oauth/token \
  -d 'client_id=OAUTH_CLIENT_ID&grant_type=assertion&assertion=YOUR_FACEBOOK_ACCESS_TOKEN'

where OAUTH_CLIENT_ID is your id of Doorkeeper’s OAuth Application (you can read more about it here), YOUR_FACEBOOK_ACCESS_TOKEN is your Facebook access token (just to test, you can generate it in Facebook’s Graph API Explorer). After running that request you’ll get something like:

{"access_token":"e457964f1a006c75b3eeee168460547d6b6ffd5983dc284571832fed3a5b4ec9","token_type":"bearer","expires_in":86400,"refresh_token":"ad91f7b6c0a85f55c1d33c187a3ad0f4e79565a8d2074e38ba9f298357e459b2","created_at":1528458832}

It works! Here’s the access_token you can use to do authorized requests to your endpoints!