3 min read

Facebook Authentication for Rails APIs

Facebook Authentication for Rails APIs

When building standard Ruby on Rails applications, it's easy to implement Facebook OAuth authentication using gems like omniauth and its various oauth strategy plugins. Unfortunately, the omniauth's default behavior doesn't lend itself very well to building API auth endpoints. Thus, we need a different solution to the problem.

The koala gem is a simple solution to this problem. It is extremely handy for retrieving Facebook access tokens that can be used to retrieve user profiles. Using this gem to implement Facebook authentication for your Rails API is very simple.

Let's say that you have a User model that you need to sign in or create using Facebook access tokens that may be passed into your API via mobile devices. Also, let's say that you have a controller called FacebookAuthenticationsController whose sole purpose is to take in a facebook_access_token and retrieve or create a user using the token. First, let's write the controller that will taken in the token.

class Api::FacebookAuthenticationsController < Api::ApiController
  respond_to :json

  def create
    facebook_access_token = params.require(:facebook_access_token)
    user = User.find_or_create_with_facebook_access_token(facebook_access_token)

    if user.persisted?
      render json: user.to_json, status: :ok
    else
      render json: user.errors, status: :unprocessable_entity
    end
  end
end

The create method takes in a facebook_access_token and tries to find or create a User using the token. If the user has persisted, meaning that the User is persisted (or existing) in the database, then we return the user data.

If not, we return what caused the user errors. If you're familiar with ActiveRecord, you'll have noticed that find_or_create_with_facebook_token is not an ActiveRecord method. This is a method that we will create in the User model itself.

Our users table in our schema.rb looks like the following.

ActiveRecord::Schema.define(version: 20170917190525) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet "current_sign_in_ip"
    t.inet "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "uid"
    t.string "provider"
    t.string "oauth_token"
    t.datetime "oauth_expires_at"
    t.string "authentication_token", limit: 30
    t.string "name"
    t.string "picture_url"
    t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end
end

The important columns in the users table for this facebook login/registration implementation are: name, picture_url, uid, provider, and oauth_token. Now, let's get to writing the find_or_create_with_facebook_token method in our User model.

class User < ApplicationRecord
  def self.find_or_create_with_facebook_access_token(oauth_access_token)
    graph = Koala::Facebook::API.new(oauth_access_token)
    profile = graph.get_object("me", fields: ["name", "email"])

    data = {
      name: profile["name"],
      email: profile["email"],
      uid: profile["id"],
      provider: "facebook",
      oauth_token: oauth_access_token,
      picture_url: "https://graph.facebook.com/#{profile['id']}/picture?type=large",
      password: SecureRandom.urlsafe_base64
    }

    if user = User.find_by(uid: data["uid"], provider: "facebook")
      user.update_attributes(data)
    else
      User.create(data)
    end
  end
end

In our method, we use the oauth_access_token along with the Koala gem to retrieve the user graph from Facebook. We retrieve the profile data from the graph and then store necessary data in a hash. We then use the uid (which really is the Facebook user id) to try to find whether the user exists in the database.

If the user exists, we update the user attributes and return that user and if the user does not exist, we create a new user using the data that we retrieved from Facebook. The reason uid is important here is because while the oauth_access_tokens will eventually expire and change, the uid from Facebook does not. Thus we use that attribute to check whether we already have the user in our database or not.

I hope that this post was helpful to those who are looking to build Facebook authentication in their Rails APIs. I like this approach because you don't have to add larger dependencies like devise and omniauth.