First, add the devise token auth gem to your Gemfile
and run bundle
to install it
gem 'devise_token_auth'
Create a route to receive your Facebook authentication request
namespace :api, path: '/', defaults: { format: 'json' } do
scope module: 'v1' do
mount_devise_token_auth_for 'Customer', at: 'auth'
post '/auth/facebook', to: 'customers/third_party_auth#create'
end
end
With the route in place, add a controller to handle it's requests, the main idea is to get the received token and asks Facebook if it is valid or not (if it is a Facebook token and if the user has granted your app Facebook access), whe these validations are all true, Facebook will return some informations that could be used to create and save a new user on your system.
module Api::V1::Customers
class ThirdPartyAuthController < ::Api::V1::ApiController
skip_before_action :authenticate_customer!
def create
response = verify_fb_access_token(params[:token])
case response.code
when '200'
handle_login(response.body)
when '400'
raise FacebookLoginError.new(response.body['error']['message'])
end
rescue MissingAddressError => e
# If you are not debugging, don't send your backtrace to the
# front end app, that may expose your system architecture
render status: 422, json: { status: 422, error: e.message, backtrace: e.backtrace }
end
private
# Get the received token, validates it on Facebook and returns it's response.
# The values assigned to the query attribute fields are the ones Facebook will
# return from oit's user
def verify_fb_access_token(token)
require 'net/http'
source = "https://graph.facebook.com/v2.11/me?access_token=#{token}"
source += '&fields=id,name,email,picture.type(large)'
resp = Net::HTTP.get_response(URI.parse(source))
FacebookResponse.new resp.code, JSON.parse(resp.body)
end
# Create a new user or signs in an existent one according to Facebook's response
def handle_login(response)
@resource = Customer.first_or_create_facebook_user(response, addresses_params)
create_client_id_and_token
# Sign in the user using Devise's sign_in method
sign_in(:user, @resource, store: false, bypass: false)
end
# Create the necessary tokens for user's authentication
def create_client_id_and_token
@client_id = SecureRandom.urlsafe_base64(nil, false)
@token = SecureRandom.urlsafe_base64(nil, false)
@resource.tokens[@client_id] = {
token: BCrypt::Password.create(@token),
expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
}
@resource.save
end
# An small class that organizes Facebook response
class FacebookResponse
attr_accessor :code, :body
def initialize(code, body)
@code = code
@body = body
end
end
# An optional class used to handle Facebook token auth requests
class FacebookLoginError < Error; end
end
end
Add first_or_create_facebook_user
method to your model Customer
class Customer < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable # , :confirmable
include DeviseTokenAuth::Concerns::User
def self.first_or_create_facebook_user(user, addresses_attributes = nil)
# Normalize Facebook email as it's an optional paramenter and may not be sent
fb_email = user['email'] || "#{user['name'].parameterize}@facebook.com"
# Find Customer by facebok_id or email
customer = find_by(provider_id: user['id'])
customer ||= find_by(email: fb_email)
if customer.present?
# If the user already has an account, we update him
# to be a Facebook user and keep going with the request.
if customer.provider_id.blank?
customer.provider = 'facebook'
customer.provider_id = user['id']
customer.save
end
else
user_data = {
name: user['name'],
email: fb_email,
provider: 'facebook',
provider_id: user['id'],
password: SecureRandom.urlsafe_base64(nil, false),
image: user['picture']['data']['url']
}
user_data[:uid] = user_data[:email]
user_data[:password_confirmation] = user_data[:password]
customer = create(user_data)
end
customer
end
end
Remember that you will need to create a Facebook application to your app inside Facebook's developers console. Your front end app still needs to make some configurations that may varies according to the language and/or plataform used.