How to Use Omniauth to Make Authenticated Calls to Services

How to use omniauth to make authenticated calls to services?

Hey, I'm the author of the OmniAuth gem. OmniAuth is meant to be used for the authentication process. In the case of OAuth providers like Netflix, this means exchanging a request token for an access token which is then used to pull user information from the API. These one-off calls are specifically designed for each provider and are not meant to be a generic API client for the given provider.

What you can do it use OmniAuth to obtain the credentials and then use another specific library for the site itself (such as ruby-netflix or anything else, I'm not sure what the best one is) to make calls. You can retrieve the access token and secret that is obtained in the authentication dance by accessing env['omniauth.auth']['credentials'], then use those to initialize the API client.

You can also use the OAuth library directly to make these calls, but I would strongly recommend just using an existing library, it will be much faster and easier. Does all of that make sense?

Authenticate user using omniauth and Facebook for a rails API?

the best way I found (after being stuck for a while on this issue ) is to do your omniauth2 (specifically in my case using satellizer angular plugin) manually...

I'll discuss the solution for Facebook as it was my case, but everything could apply to any other provider.

first you have to know how omniauth2 works (as documented for humans here)...

  1. Client: Open a popup window for user to authenticate.
  2. Client: Sign in (if necessary), then authorize the application.
  3. Client: After successful authorization, the popup is redirected back to your app. with the code (authorization code) query string parameter

the redirect back url must match your front-end app url not the back-end url and it must be specified in your facebook app configurations


  1. Client: The code parameter is sent back to the parent window that opened the popup.
  2. Client: Parent window closes the popup and sends a POST request to backend/auth/facebook with code parameter.
  3. Server: code (Authorization code) is exchanged for access token

here is described in details how to exchange the code for an access-token from facebook developers documentation


  1. Server: use the access-token retrieved in step 6 to retrieve the User's info.

  2. VOILA you've got yourself a user you can merge/create account for/link with other oauth providers/etc. but bear in mind that user can revoke some of the permissions (like email, facebook supports revoking some of the permissions)...


(enough talking, show me some code)

First you have to add HTTParty gem to your Gemfile

gem 'httparty'  # Makes http fun again (http client)

I've added this gist which contains the flow for step (6, 7 and 8) those are the most problematic steps and are not documented almost anywhere.

the gist exports 2 main methods:

Omniauth::Facebook.authenticate(authorization_code)

which is used to authenticate the user with facebook and return the user_info, long_live_access_token (valid for 60 days)

Omniauth::Facebook.deauthorize(access_token)

which is used to de-authorize/revoke the access_token and application permissions on facebook...

This is used for special requirement I have, when the user revoke the email permission requested on facebook login... we revoke the whole application permissions... this will prompt the user in the next login as if it's his first login ( no need to go to facebook apps and manually revoke the application)...

here is how it's used in the controller

user_info, access_token = Omniauth::Facebook.authenticate(params['code'])
if user_info['email'].blank?
Omniauth::Facebook.deauthorize(access_token)
end

That's it... now if you are interested in the internals of the implementation... here is the code as seen in the gist. (added for reference)
Feel free to fork it, edit it, help making it better.

require 'httparty'

module Omniauth
class Facebook
include HTTParty

# The base uri for facebook graph API
base_uri 'https://graph.facebook.com/v2.3'

# Used to authenticate app with facebook user
# Usage
# Omniauth::Facebook.authenticate('authorization_code')
# Flow
# Retrieve access_token from authorization_code
# Retrieve User_Info hash from access_token
def self.authenticate(code)
provider = self.new
access_token = provider.get_access_token(code)
user_info = provider.get_user_profile(access_token)
return user_info, access_token
end

# Used to revoke the application permissions and login if a user
# revoked some of the mandatory permissions required by the application
# like the email
# Usage
# Omniauth::Facebook.deauthorize(access_token)
# Flow
# Send DELETE /me/permissions?access_token=XXX
def self.deauthorize(access_token)
options = { query: { access_token: access_token } }
response = self.delete('/me/permissions', options)

# Something went wrong most propably beacuse of the connection.
unless response.success?
Rails.logger.error 'Omniauth::Facebook.deauthorize Failed'
fail Omniauth::ResponseError, 'errors.auth.facebook.deauthorization'
end
response.parsed_response
end

def get_access_token(code)
response = self.class.get('/oauth/access_token', query(code))

# Something went wrong either wrong configuration or connection
unless response.success?
Rails.logger.error 'Omniauth::Facebook.get_access_token Failed'
fail Omniauth::ResponseError, 'errors.auth.facebook.access_token'
end
response.parsed_response['access_token']
end

def get_user_profile(access_token)
options = { query: { access_token: access_token } }
response = self.class.get('/me', options)

# Something went wrong most propably beacuse of the connection.
unless response.success?
Rails.logger.error 'Omniauth::Facebook.get_user_profile Failed'
fail Omniauth::ResponseError, 'errors.auth.facebook.user_profile'
end
response.parsed_response
end

private

# access_token required params
# https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.3#confirm
def query(code)
{
query: {
# The authorization_code we want to exchange for the access_token
code: code,
# This must match the redirectUrl registerd in the facebook app.
# You can save it to ENV['WEB_APP_URL'] if you have multiple facebook apps for development and testing
# so you can support testing app on development and production app on production env.
redirect_uri: "http://localhost:9000/",
client_id: ENV['FB_APP_ID'], # Facebook appId
client_secret: ENV['FB_APP_SECRET'], # Facebook app secret (must not exist on front-end app for security)
}
}
end
end
end

here is another nodejs tutorial implementing oauth for instagram that helped me understand how oauth2 is working (added for reference)

Rails 4 - Devise Omniauth and allowing a single user to authenticate with multiple social media strategies

Without being able to see all of your code, I just created a shell app that runs with multiple providers. I just followed the steps from the tutorial you mentioned at sourcey. Here is the link to my repo.

You should be able to just clone it and run it by entering your app's keys and secret tokens from facebook, twitter, and linkedin in the devise.rb initializer. To get this to work locally you will need to make sure that the callback url on twitter is set to http://127.0.0.1:3000/.

If you wanted to give a user the option to add their own omniauth account (identity) instead of having it done automatically through app authorization you could just make a form for the user to enter the numeric uid and create the identity your self in the controller or back end like this:

new_identity = Identity.new
new_identity.user_id = "current user's id"
new_identity.provider = "facebook"
new_identity.uid = "0123456789"
new_identity.save!

The user would have to get their numeric uid from the site and enter it themselves.

Devise + Omniauth, a provider that does not have a callback phase

I implemented this using a custom Devise strategy, along with the httpclient gem. From there, it was as simple as calling out to the API endpoint and parsing the XML to check and see if there was a valid user trying to sign in. I created password-less users on my side if the user had never signed in before.

Building an OmniAuth strategy with a custom workflow

Apparently this is quite easy to do.

The OmniAuth strategy

module OmniAuth
module Strategies
class Service
include OmniAuth::Strategy

def request_phase
redirect AUTHENTICATION_URL
end

uid { @user_details.user_id }

def extra
@user_details # Return a hash with user data
end

def callback_phase
# Configure Service SDK
@user_details = Service.user_data # Make SDK call to get user details
super
end
end
end
end

The App

1) Add a login button with the authentication URL:

<%= link_to 'Login', 'auth/service' %>

2) Add a callback route

get '/auth/service/callback', to: 'sessions#create'

3) Handle the callback response in the controller

class SessionsController < ApplicationController
def create
@user = User.find_or_create_by(service_id: auth_hash.uid)
# Handle @user
end
end

Use separate authentication model with Devise on Rails

Have been recently working on a project where I was using Devise to keep user's tokens for different services. A bit different case, but still your question got me thinking for a while.

I'd bind Devise to Account model anyway. Why? Let's see.

Since my email is the only thing that can identify me as a user (and you refer to Account as the User) I would place it in accounts table in pair with the password, so that I'm initially able do use basic email/password authentication. Also I'd keep API tokens in authentications.

As you've mentioned, OmniAuth module needs to store provider and id. If you want your user to be able to be connected with different services at the same time (and for some reason you do) then obviously you need to keep both provider-id pairs somewhere, otherwise one will simply be overwritten each time a single user authenticates. That leads us to the Authentication model which is already suitable for that and has a reference to Account.

So when looking for a provider-id pair you want to check authentications table and not accounts. If one is found, you simply return an account associated with it. If not then you check if account containing such email exists. Create new authentication if the answer is yes, otherwise create one and then create authentication for it.

To be more specific:

#callbacks_controller.rb
controller Callbacks < Devise::OmniauthCallbacksContoller
def omniauth_callback
auth = request.env['omniauth.auth']
authentication = Authentication.where(provider: auth.prodiver, uid: auth.uid).first
if authentication
@account = authentication.account
else
@account = Account.where(email: auth.info.email).first
if @account
@account.authentication.create(provider: auth.provider, uid: auth.uid,
token: auth.credentials[:token], secret: auth.credentials[:secret])
else
@account = Account.create(email: auth.info.email, password: Devise.friendly_token[0,20])
@account.authentication.create(provider: auth.provider, uid: auth.uid,
token: auth.credentials[:token], secret: auth.credentials[:secret])
end
end
sign_in_and_redirect @account, :event => :authentication
end
end

#authentication.rb
class Authentication < ActiveRecord::Base
attr_accessible :provider, :uid, :token, :secret, :account_id
belongs_to :account
end

#account.rb
class Account < ActiveRecord::Base
devise :database_authenticatable
attr_accessible :email, :password
has_many :authentications
end

#routes.rb
devise_for :accounts, controllers: { omniauth_callbacks: 'callbacks' }
devise_scope :accounts do
get 'auth/:provider/callback' => 'callbacks#omniauth_callback'
end

That should give you what you need while keeping the flexibility you want.



Related Topics



Leave a reply



Submit