How to use omniauth to make authenticated calls to services? - ruby-on-rails

I've received a token / secret from a service using OmniAuth and can store it for users, but I'm stuck as to how to actually use these to call a service.
The closest thing I've seen to this question is here but the way he's solved that there doesn't feel right. I feel like OmniAuth likely does this all for you if you know what you're doing.
Netflix has a pretty involved auth process, so I was hoping to skirt all of this by using OmniAuth to abstract me from all of this.
Given that I have a token and secret for a user, how to use these in calling a service like Netflix?
Many thanks :)

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?

OmniAuth is all about authentication; you should probably look at another gem for making actual calls to the service. E.g., for Facebook, I use the OAuth2 gem and code like the following:
module Facebook
class Client < OAuth2::Client
# Return a new OAuth2::Client object specific to the app.
def initialize
super(
APP_CONFIG[:facebook][:api_key],
APP_CONFIG[:facebook][:app_secret],
:site => 'https://graph.facebook.com',
:parse_json => true
)
end
end
class Token < OAuth2::AccessToken
# Return a new OAuth2::AccessToken specific to the app
# and the user with the given token.
def initialize(token)
super(
Facebook::Client.new,
token
)
end
end
end
access_token = Facebook::Token.new(users_fb_token)
url = "https://graph.facebook.com/#{user_fb_id}/feed"
response = access_token.post(url, :message => "My update")
Note that there are gems for popular services, like Facebook and Twitter, that can manage the behind-the-scenes things like creating tokens, managing URLs, etc. For Netflix, you might check the following:
https://github.com/tiegz/ruby-netflix
https://github.com/rares/netflix
http://code.google.com/p/flix4r/
Also keep in mind that OmniAuth just returns the service data to you; you're free to store it and use it how you will (Devise has it's own pattern for OmniAuth that you might butt heads with if you try to go outside the lines). The other question you linked doesn't look too far fetched to me.

Related

Shopify OAuth2 - Custom scopes per Shop

I'm using Ruby on Rails, and connecting to the Shopify REST Admin API in my app, to retrieve information for stores. We use the Shopify OAuth2 gem, which handles authentication and access scopes for the API, using the OmniAuth middleware:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :shopify,
ENV["SHOPIFY_API_KEY"],
ENV["SHOPIFY_API_SECRET"],
scope: 'read_orders,read_products',
setup: lambda { |env|
strategy = env['omniauth.strategy']
shopify_auth_params = Rack::Utils.parse_query(env['QUERY_STRING'])
shop = if shopify_auth_params.present?
"https://#{shopify_auth_params['shop']}"
else
''
end
strategy.options[:client_options][:site] = shop
}
end
I want to change which scopes are sent to Shopify during authentication, so some stores get one set of scopes and another store gets a different set. For example, some stores will request the scopes:
read_orders, read_products
While another store requests:
read_orders, read_products, read_inventory
The reason for this is to allow users to choose when they would like to upgrade the app themselves, and not be forced to do so when changing scopes needed by the app.
How I can pass in additional information dynamically?
I solved this by following along on this GitHub issue: https://github.com/Shopify/omniauth-shopify-oauth2/issues/60
You can pass in the scopes dynamically through the session, and set it in the setup block of OmniAuth:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :shopify,
ShopifyApp.configuration.api_key,
ShopifyApp.configuration.secret,
setup: lambda { |env|
strategy = env['omniauth.strategy']
session = strategy.session.with_indifferent_access
env['omniauth.strategy'].options[:scope] = session['shopify.oauth.scope']
...
}
It might be smarter to think of this in different terms. You probably do not want to use scopes in the world of differentiating your App. Instead, you will focus on actual functionality delivered to the merchant. Pay more, get more. So when you install the App, to make your life easier, you have one set of scope for all installs. You avoid the dreaded modal popup asking for new scopes later, likely resulting in confusion and uninstalls.
Even if the most restricted App has maximum scope, the merchant cannot do anything with that if you architected your App to limit their functionality. So you might want to build in to your App just that. You decide what the App delivers by inspecting the current subscription plan they are paying for instead of worrying about scope.
TL:DR; using scope to decide what your App does is a bad idea.

How to validate that a user owns the requested resource through Rails API when using devise_token_auth

I am building an API-only (for now) Rails app to serve as the back end for an Android app I'm building. I was previously using Firebase but wanted to do more processing on application data and I didn't want to bloat the mobile client with all the logic, so I am moving away from Firebase's real-time database and backing the application data using Rails. I was also using Firebase's authentication which is very straightforward. But it seems less complex for the overall system to keep all of this functionality in one place, so I'd like to perform auth and user management in the Rails app as well.
I have installed devise_token_auth (seen here) and finished the basic configuration. The /auth route works correctly if params are provided and creates a user. sign_in and sign_out both successfully create sessions and return some header information. The important parts returned are client, access-token, and uid, which I need to use in future calls to the API. I believe these are invalidated and replaced with each subsequent call. At this part of the flow is where I'm not sure how to proceed. I don't understand how the data in these headers is associated with the user who signed in and how I can validate that they own a resource they request. To summarize the question another way:
How can I sign a user into the API and then validate which user is making subsequent API calls?
My Android app is a task manager, so I need to be able to validate for example that if user 1 requests task 3, that they own that resource. I'm also unsure how to direct index calls using the API. That is, when the index endpoint is hit (/tasks), how can I identify from the data in the API headers which user's tasks I should retrieve.
I haven't been able to find any solid tutorials or samples using devise_token_auth so I'm having trouble stitching together the interaction between the pieces I have now. I know this is a meaty question - thanks in advance for any guidance.
How can I [...] validate which user is making subsequent API calls?
With the current_user method. This is a built-in feature to the devise_token_auth gem.
I need to be able to validate for example that if user 1 requests task 3, that they own that resource
There are many different approaches you could take. You could just write some custom logic in each controller action, using the current_user method (and return 403 forbidden if necessary).
Or, you could use a popular "framework" solution for this such as CanCanCan or Pundit. I, and probably most of the modern community, would recommend Pundit.
I highly advise you to read that library's README, as it's extremely helpful. But for the example above, you could write something like this:
class TasksController
def show
task = Task.find(params[:id])
authorize(task) # !!!
render task
end
end
# app/policies/task_policy.rb
class TaskPolicy
def show?
record.user == user
end
end
(Note that by default, the "user" in Pundit policies calls the method: current_user. This is all explained in the project's README.)
when the index endpoint is hit (/tasks), how can I identify from the data in the API headers which user's tasks I should retrieve
Again, this is all handled as part of Pundit's standard features. You just need to define a TaskPolicy::Scope and call policy_scope(Task) in the controller - as explained here.

Using the new Devise for authentication for a mobile app

I am trying to implement user authentication using Devise for my Rails/iOS app. I am having trouble since I've mostly been a "user" of Devise and was using it for pure web apps so didn't need to bother so much with what goes on behind the scenes. Now that I have to build authentication for an API based app, it's entirely a different world.
I've read every single tutorial on the web that deals with this topic (most of them are outdated due to the fact that token_authenticatable has been deprecated) but still having trouble understanding what I need to do.
I also read the original GitHub gist talking about this issue and still don't understand what they are talking about.
I'm sure there are people out there just like me who've been just a "user" of Devise so don't really know what goes on behind the scenes.
Can anyone provide a concise solution to implementing an API based authentication system for a mobile app? I mean it can't be that complex, Devise used to be so awesome since all i needed to do was run rails generate, but this has been nightmare for me.
I am working on same things as you want,
for this you have to use token authentication rather than simple
Devise, add following gem in gemfile
# Use device for authentication
gem 'devise'
gem 'simple_token_authentication'
follow documention simple_token_authentication
Use Api like this
curl -v https://example.com/users/sign_in -X POST -H "Accept: application/json" -H "Content-Type: application/json" -d '{"user": {"login": "7838712847" ,"password": "8489", "mobile_type": "ios", "mobile_key": "APA91bG6G4UvjeCWvb8mMAH1ZO3I-8FB3nkRPyCHnwZiXgd16HK18GgoV5n7gjJtZNs038iaFGutzdxnhes3WyaXEX52-xmOyvuEK8S1abBdpuhD9AD5bzLWeu-1Ow_yZRTVg3Nypz1z"}}'
I am using mobile number to login so customize gem according your need
please let me know if it is not working (mail me: er.mukeshsharma.rj21#gmail.com)
Here is an approach that works excellent for me, when using Devise for authentication in a Rails app. If tests for a token first (you can set the token by any iOS, Android, ... app) and falls back to the default authentication method for your web users.
Rails
Add your own token to the user model, by adding an :api_token string column and fill that with a unique value per user. Using a Digest::SHA1 of some user data (like id + email) is a good starting point, but you can (and should) go as crazy as you like when it comes to generating a unique token.
Create a method for authentication over that token. You can add it to your main ApplicationController for easy access (don't forget to put the method in your private section of the controller);
def authenticate_user_by_token
#api_token = request.headers['HTTP_AUTHORIZATION']
if #api_token.present? && #user = User.find_by_api_token(#api_token)
sign_in #user
return #user
else
return false
end
end
Next create a (private) method and chain this method to the devise before filter method you are using (like :authenticate_user! for example). Put it in the same controller as the method above for easy access;
def authenticate_by_token_or_devise!
return authenticate_user! unless authenticate_user_by_token
end
Now Replace your current before_filter call from :authenticate_user! to the newly created one; :authenticate_by_token_or_devise!, like so;
before_filter :authenticate_by_token_or_devise!
Or, starting from rails 4 (Rails 4: before_filter vs. before_action), use before_action;
before_action :authenticate_by_token_or_devise!
iOS
Now all you have to do is add that token to your iOS app. Depending on the framework that you use in your app, this might be different then the code below.
I use AFNetworking (https://github.com/AFNetworking/AFNetworking) in this example. This is how you set the Authorisation header token in your AFHTTPRequestOperationManager so it gets added to every request you make.
NSString *apiToken = #"your-token-here";
[[_manager requestSerializer] setValue:apiToken forHTTPHeaderField:#"Authorization"];
Optional
Additionally, you can create a before filter method that allows access to token-based authentication only (e.g. if you have a set of /api routes that you only want to be accessed using the token) like this;
def authenticate_user_by_token!
if !authenticate_user_by_token
render nothing: true, status: :unauthorized and return
end
end
Recently, we also had to set up token based authentication for our webapp (for API access) - and we also stumbled upon the fact that it has been removed from Devise.
We went with Simple Token Authentication which worked just beautifully.
When I recently implemented an API, I grudgingly followed a suggestion to use Warden, a rack-based authentication gem. My sense was that an authentication gem that required you to write your own authentication was broken. But this gem provides just the right level of control. My only complaint is that the gem don't handle POST parameters well. I was able to work around it, but that kind of concern should be (IMO) handled by the gem.
Having used it, I highly recommend this gem for any scenario requiring non-generic authentication. Rolling your own authentication strategies is a joy because (a) it's pretty simple and (b) you aren't bound by other devs' assumptions.
To help you get started, here is my config/initializers/warden.rb file.
You can use a combination of the devise gem and doorkeeper gem to support web and mobile authentication.
For example, I used devise for signing up users and handling forget password and email confirmation flow. For mobile clients, I used the doorkeeper gem as a oauth2 provider to protect my apis. There are many oauth2 grant flows supported by the doorkeeper gem and I suggest you can take a look at those.
Here's a link! to get started

Allowing rails api to return json

I'm lost here- don't even really know what questions to ask.
I built an api as described here : http://railscasts.com/episodes/350-rest-api-versioning?view=asciicast
It's on a site that requires login.
I can access the api through the browser when I am logged in, no problem. Here are my routes:
## API
namespace :api do
namespace :v1 do
resources :users, :sessions
end
end
What I've been asked to do is to make it so that an outside party can request the json with a Api key and optional query parameters and userId
I've tried to curl the site (https) and get redirected to a logout, even with username and password.
My boss suggested a public form on the root url that accepts the userId, apikey and params, and can be curled and will return the data via the parameters posted from curl.
I have no idea how to do this, or even to allow the data to be called without loggin in. I looked at this other screen cast http://railscasts.com/episodes/353-oauth-with-doorkeeper (it's a paid version) but his interactions are allowing another rails app to interact with his original app.
Basically, where should I look for information on how to accomplish this? Any other suggestions, or more information that I could give to make my question more clear?
Thank you for your time.
More RailsCasts! Ryan Bates has you covered in http://railscasts.com/episodes/352-securing-an-api?view=asciicast
So to boil it down:
you need to determine if the clients calling your API need to identify themselves, or if they also need to authenticate somehow
you need to determine if the users of the client (that's calling your API) need to authenticate with your system -- that is, are there "users"?
Some APIs allow callers to pass an API Key as part of the query; others require a more sophisticated process of authenticating, typically OAuth, which is a little trickier.
Some APIs provide user-specific information, in which case you'll need a way to make sure the user can log in, change their password, recall a forgotten password and so on. There's a good RailsCast for that, too (or you can use the Devise gem, although I wouldn't recommend it if you're mainly implementing an API -- not Devise's strong suit).
I am guessing you know this part, but all curl does is simulate the HTTP requests your clients will be making to your API, and (with the --include option) can show you the information about the response returned -- headers, cookies, and so on.
Google "rails api authentication" for more.

Rails: Devise Authentication from an ActiveResource call

My two rails applications(app1, app2) are communicating using active resource.
app1 calls app2 create a user inside app2. app2 would create the user and would like app1 then redirect the user to app2's authenticated pages.
going from app1 to app2 would invariably ask the user to log in.
I was looking for a way to avoid this login step in app2, instead make the user log in during the first active resource call to create user, and somehow get the authentication token written.
Authentication is done using Devise. Is there anything built into Devise that support this?
Is passing around the authentication token the way to go?
You are trying to implement a form of Single Sign-On service (SSO) (sign in with app1, and be automatically authenticated with app2, app3...). It is unfortunately not a trivial task. You can probably make it work (maybe you already did), but instead of trying to reinvent the wheel, why not instead integrate an existing solution? Or even better, a standard protocol? It is actually relatively easy.
CAS server
RubyCAS is a Ruby server that implements Yale University's CAS (Central Authentication Service) protocol. I had great success with it.
The tricky part is getting it to work with your existing Devise authentication database. We faced the same problem, and after some code diving, I came up with the following, which works like a charm for us. This goes in your RubyCAS server config, by default /etc/rubycas-server/config.yml. Of course, adapt as necessary:
authenticator:
class: CASServer::Authenticators::SQLEncrypted
database:
adapter: sqlite3
database: /path/to/your/devise/production.sqlite3
user_table: users
username_column: email
password_column: encrypted_password
encrypt_function: 'require "bcrypt"; user.encrypted_password == ::BCrypt::Engine.hash_secret("#{#password}", ::BCrypt::Password.new(user.encrypted_password).salt)'
enter code here
That encrypt_function was pain to figure out... I am not too happy about embedding a require statement in there, but hey, it works. Any improvement would be welcome though.
CAS client(s)
For the client side (module that you will want to integrate into app2, app3...), a Rails plugin is provided by the RubyCAS-client gem.
You will need an initializer rubycas_client.rb, something like:
require 'casclient'
require 'casclient/frameworks/rails/filter'
CASClient::Frameworks::Rails::Filter.configure(
:cas_base_url => "https://cas.example.com/"
)
Finally, you can re-wire a few Devise calls to use CAS so your current code will work almost as-is:
# Mandatory authentication
def authenticate_user!
CASClient::Frameworks::Rails::Filter.filter(self)
end
# Optional authentication (not in Devise)
def authenticate_user
CASClient::Frameworks::Rails::GatewayFilter
end
def user_signed_in?
session[:cas_user].present?
end
Unfortunately there is no direct way to replace current_user, but you can try the suggestions below:
current_user with direct DB access
If your client apps have access to the backend users database, you could load the user data from there:
def current_user
return nil if session[:cas_user].nil?
return User.find_by_email(session[:cas_user])
end
But for a more extensible architecture, you may want to keep the apps separate from the backend. For the, you can try the following two methods.
current_user using CAS extra_attributes
Use the extra_attributes provided by the CAS protocol: basically, pass all the necessary user data as extra_attributes in the CAS token (add an extra_attributes key, listing the needed attributes, to your authenticator in config.yml), and rebuild a virtual user on the client side. The code would look something like this:
def current_user
return nil if session[:cas_user].nil?
email = session[:cas_user]
extra_attributes = session[:cas_extra_attributes]
user = VirtualUser.new(:email => email,
:name => extra_attributes[:name],
:mojo => extra_attributes[:mojo],
)
return user
end
The VirtualUser class definition is left as an exercise. Hint: using a tableless ActiveRecord (see Railscast #193) should let you write a drop-in replacement that should just work as-is with your existing code.
current_user using an XML API on the backend and an ActiveResource
Another possibility is to prepare an XML API on the users backend, then use an ActiveResource to retrieve your User model. In that case, assuming your XML API accepts an email parameter to filter the users list, the code would look like:
def current_user
return nil if session[:cas_user].nil?
email = session[:cas_user]
# Here User is an ActiveResource
return User.all(:params => {:email => email}).first
end
While this method requires an extra request, we found it to be the most flexible. Just be sure to secure your XML API or you may be opening a gapping security hole in your system. SSL, HTTP authentication, and since it is for internal use only, throw in IP restrictions for good measure.
Bonus: other frameworks can join the fun too!
Since CAS is a standard protocol, you get the added benefit of allowing apps using other technologies to use your Single Sign-On service. There are official clients for Java, PHP, .Net and Apache.
Let me know if this was of any help, and don't hesitate to ask if you have any question.

Resources