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_token
s 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
.