Rails custom consumer oauth strategy - ruby-on-rails

I'm new to oauth and api integrations, and am having a hell (can I say that here) of a time trying to figure it out.
I'd like to connect my rails app to Magento (a php ecommerce cart).
They have some basic docs here:
http://www.magentocommerce.com/api/rest/authentication/oauth_authentication.html
While I understand the idea of oauth in principle, I have no idea how to implement a custom solution. I've used a few gems (ex: omniauth) to connect to Twitter, and that was fine, but I just don't know how to create my own strategy for connecting to Magento.
Does anyone know how to do it? Is there a walk-through or screencast somewhere I can use?
If not, what tools or approaches might you recommend for me to figure it out -- if only by trial and error?
Thanks in advance for your help!

Here are detailed instructions from the repo of the omniauth-magento gem / strategy I created:
Setting up Magento
Consumer key & secret
Set up a consumer in Magento and write down consumer key and consumer secret
Privileges
In the Magento Admin backend, go to System > Web Services > REST Roles, select Customer, and tick Retrieve under Customer. Add more privileges as needed.
Attributes
In the Magento Admin backend, go to System > Web Services > REST Attributes, select Customer, and tick Email, First name and Last name under Customer > Read. Add more attributes as needed.
Setting up Rails
Parts of these instructions are based on these OmniAuth instructions, which you can read in case you get stuck.
Devise
Install Devise if you haven't installed it
Add / replace this line in your routes.rb. This will be called once Magento has successfully authorized and returns to the Rails app.
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks"}
Magento oAuth strategy
Load this library into your Gemfile gem "omniauth-magento" and run bundle install
Modify config/initializers/devise.rb:
Devise.setup do |config|
# deactivate SSL on development environment
OpenSSL::SSL::VERIFY_PEER ||= OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
config.omniauth :magento,
"ENTER_YOUR_MAGENTO_CONSUMER_KEY",
"ENTER_YOUR_MAGENTO_CONSUMER_SECRET",
{ :client_options => { :site => "ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH" } }
# example:
# config.omniauth :magento, "12a3", "45e6", { :client_options => { :site => "http://localhost/magento" } }
Optional: If you want to use the Admin API (as opposed to the Customer API), you need to overwrite the default authorize_path like so:
{ :client_options => { :authorize_path => "/admin/oauth_authorize", :site => ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH } }
In your folder controllers, create a subfolder users
In that subfolder app/controllers/users/, create a file *omniauth_callbacks_controller.rb* with the following code:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def magento
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_magento_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "magento") if is_navigational_format?
else
session["devise.magento_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
User model & table
Here's an example of useful Magento information you can store in your User table once you have created these columns:
email
first_name
last_name
magento_id
magento_token
magento_secret
Optional: You might want to encrypt *magento_token* and *magento_secret* with the *attr_encrypted gem* for example (requires renaming magento_token to encrypted_magento_token and magento_secret to encrypted_magento_secret).
Set up your User model to be omniauthable :omniauthable, :omniauth_providers => [:magento] and create a method to save retrieved information after successfully authenticating.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable, :timeoutable,
:omniauthable, :omniauth_providers => [:magento]
def self.find_for_magento_oauth(auth, signed_in_resource=nil)
user = User.find_by(email: auth.info.email)
if !user
user = User.create!(
first_name: auth.info.first_name,
last_name: auth.info.last_name,
magento_id: auth.uid,
magento_token: auth.credentials.token,
magento_secret: auth.credentials.secret,
email: auth.info.email,
password: Devise.friendly_token[0,20]
)
else
user.update!(
magento_id: auth.uid,
magento_token: auth.credentials.token,
magento_secret: auth.credentials.secret
)
end
user
end
end
Link to start authentication
Add this line to your view <%= link_to "Sign in with Magento", user_omniauth_authorize_path(:magento) %>
Authenticating
Start your Rails server
Start your Magento server
Log into Magento with a customer account (or admin account if you want to use the Admin API)
In your Rails app, go to the view where you pasted this line <%= link_to "Sign in with Magento", user_omniauth_authorize_path(:magento) %>
Click on the link
You now should be directed to a Magento view where you are prompted to authorize access to the Magento user account
Once you have confirmed, you should get logged into Rails and redirected to the Rails callback URL specified above. The user should now have magento_id, magento_token and magento_secret stored.
Making API calls
Create a class that uses magento_token and magento_secret to do API calls for instance in *lib/magento_inspector.rb*. Example:
class MagentoInspector
require "oauth"
require "omniauth"
require "multi_json"
def initialize
#access_token = prepare_access_token(current_user) # or pass user in initialize method
#response = MultiJson.decode(#access_token.get("/api/rest/customers").body) # or pass query in initialize method, make sure privileges and attributes are enabled for query (see section at top)
end
private
# from http://behindtechlines.com/2011/08/using-the-tumblr-api-v2-on-rails-with-omniauth/
def prepare_access_token(user)
consumer = OAuth::Consumer.new("ENTER_YOUR_MAGENTO_CONSUMER_KEY", "ENTER_YOUR_MAGENTO_CONSUMER_SECRET", {:site => "ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH"})
token_hash = {:oauth_token => user.magento_token, :oauth_token_secret => user.magento_secret}
access_token = OAuth::AccessToken.from_hash(consumer, token_hash)
end
end
Make sure Rails loads files in the folder where this class is placed. For the lib folder, put this in config/application.rb: config.autoload_paths += Dir["#{config.root}/lib/**/"]
Perform query MagentoInspector.new
Extend class to suit your needs

I get this kind of guttural response when I hear the M-word (Magento), having spent several months attempting to make it do anything useful. But... assuming you have no choice, and assuming Magento offers a standard OAuth server, then you should be able to use OmniAuth to connect to Mag...blurrgh..ento.
Magento will need to provide several tokens, client_id and client_secret. You use these to request an access token for your app. Once you have it, you can reuse it semi-permanently. OmniAuth might be able to help you with that.
Once you have the access token, you'll need to pass an HTTP header that looks like Authentication: OAuth <access-token> with every request you make to the service.
Requests are made using standard https (I would hope, vs http) to the server. Within Rails you could roll your own REST client (using Net::HTTP and friends), but you might find a gem like RestClient, linked here. There are others out there -- check The Ruby Toolbox for more.
Does that get you started?

Related

Restrict Login with Google OAuth2.0 to Specific Whitelisted Domain Name on Ruby

EDIT: I changed how I'd like to do this I think I will use a MySQL table to whitelist the devise logins using google. The changed question is posted here: Restrict Login with Google OAuth2.0 and Devise to Specific Whitelist Table using Ruby
Alright so I am trying to get restricted authentication for my ruby on rails website using Devise and Omni-Auth2 and only google. Everything is working so far, but I only want emails coming from a certain domain to be accepted. I am open to anyway to do this.
I have done some googling but it seems some PHP users have a bit more local files than I do, maybe because of using the google API client locally? I'm not exactly sure, as I am quite new to coding in general and surprised I made it this far.
Here is an example: Google Oauth2.0 with Python: How do I limit access to a specific domain?
And here: Restrict Login Email with Google OAuth2.0 to Specific Domain Name
Both seem to use the "hd:domain" or something similar, but there seems to be issues with that plus I'm not sure how I would impliment it in my app.
Now for some more info, I am only using the gem devise and omniauth-google-oauth2 (https://github.com/zquestz/omniauth-google-oauth2) I feel like theres a way to do it with that gem but still not entirely sure. Any help would be appreciated if I can post any more info let me know.
My omniauth_callbacks_controller:
class User::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
I know this question is old but answering it just for reference. You need to change config/initializer/omniauth.rb and add "hd" to provider.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["google_client_id"], ENV["google_client_secret"],
{
hd: 'domain.com'
}
end
Why don't you just add a validation to the Model to restrict the domain?
validates :email,
presence: true,
uniqueness: true,
format: {
message: 'domain must be example.com',
with: /\A[\w+-.]+#example.com\z/i
}
Other user answered here:
Restrict Login with Google OAuth2.0 and Devise to Specific Whitelist Table using Ruby

Multiple, Simultaneous Oauth in a Rails Application?

My end goal is for users to have multiple 3rd party authentications at the same time.
Right now, I am using Devise to create users. Users can sign up via email or facebook or google and it works. But now, after they have already signed up, I need them to also verify with, say, youtube or soundcloud. So the user was created with devise, but I also need them to verify with other things.
Since Devise hogs omniauth for it's own purposes, I can't use omniauth on the side.
As I see it I have three options:
Try to monkeypatch devise and get it to allow multiple authentications at the same time on one user
Do oauth by hand on the side adjacent to current Devise implementation
Scrap Devise and do something different
I would greatly appreciate any advice or other options
I think this may be what you need: http://blog.joshsoftware.com/2010/12/16/multiple-applications-with-devise-omniauth-and-single-sign-on/
They open sourced their code too!
Provider: https://github.com/joshsoftware/sso-devise-omniauth-provider
Client: https://github.com/joshsoftware/sso-devise-omniauth-client
Or even better, check out this: http://communityguides.heroku.com/articles/16
Try to monkeypatch devise and get it to allow multiple authentications at the same time on one use
You don't need to monkeypatch devise --- you can have your own oauth controller the has
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# handle if already a twitter user
# handle if a new user
# use the `sign_in user` to sign_in the user
end
def twitter
# handle if already a facebook user
# handle if a new user
end
end
and use it in routes
devise_for :user,
:controllers => {
:omniauth_callbacks => "users/omniauth_callbacks"
}

How does omniauth-ldap work?

I'm currently working on a rails project in which I am in charge of user authentication.
We've decided to use third party authentication and I tried following an example setup.
The example is done by Kevin Thompson and is called example.
According to the LDAP sever's documentation, the steps I need to do are:
Connect to the LDAP server.
Bind anonymously (no DN and password).
Search for the LDAP entry using the username
Retrieve the DN for the username if found.
Rebind with the user's DN and password that they supplied.
If this rebind succeeds, the user is authenticated.
I've followed Thompson's example, except that I'm not using nifty; using devise for user management and omniauth-ldap for authentication. However, it's not quite working, and I'm wondering if it has to do with a discrepancy between what the server documentation tells me to do and what omniauth-ldap is actually doing...
Specifically, my problem is that I always get an "Invalid credentials" error.
Is this because of a mismatch between what I need to do and what omniauth-ldap is doing?
Advice or suggestions are greatly appreciated!
A little more information about how I've set up (to maintain anonymity, I replaced some things)
I can post more of my code upon request.
config/initializers/devise.rb:
config.omniauth :ldap,
:host => 'ldap1.its.domain.ext',
:base => 'ou=People, dc=domain, dc=ext',
:port => 389,
:attrs => 'uid',
:method => :plain,
:uid => 'uid'
app/controllers/users/omniauth_callbacks_controller.rb:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :verify_authenticity_token
def ldap
ldap_return = request.env["omniauth.auth"]["extra"]["raw_info"]
username = ldap_return.uid[0].to_s
if #user = User.find_by_username(username)
sign_in_and_redirect #user
else
#user = User.create(:username => username,)
sign_in_and_redirect #user
end
end
end
I just solved a similar issue.
First, you'll need to determine if your domain allows anonymous binding. This was not allowed in my case. There is an great pull request for using the current user to bind, dorren/omniauth-ldap. Otherwise, you'll need a system account. Just to get moving initially, I used my username(i.e., userPrincipalName)/password for :bind_dn and :password.
Secondly, for LDAP authentication, the two uid values that are used to authenticate are sAMAccountName(username) or userPrincipalName(username#ldap.domain.ext). I found that my system uses userPrincipalName. To prevent the user from entering that in, I just concatenated the domain prior to submitting the form.
Try this config.
config.omniauth :ldap,
:host => 'ldap.domain.ext',
:base => 'dc=ldap, dc=domain, dc=ext',
:port => 389,
:method => :plain,
:uid => 'userPrincipalName',
:bind_dn => 'bind_dn',
:password => 'password'
I believe :bind_dn can be of the form:
'CN=LastName\, FirstName, OU=People, DC=ldap, DC=domain, DC=ext'
OR
'username#ldap.domain.ext'
I also found writing a ruby script using Net::LDAP, to bind and search really helped me learn about Active Directory as well, since I had no knowledge of this subject prior to this task.
I can't add this as a comment, as I don't have 50 reputation yet, however I found this which may be of use to some people here.
http://blackfistsecurity.blogspot.com.au/2011/12/rails-authentication-using-devise-and.html
I was originally trying to use Omniauth and Omniauth-LDAP without anything else, but the lack of documentation on omniauth-ldap's part makes things difficult.
edit:
Instead of using Omniauth-LDAP, I ended up opting for a vanilla devise install, and wrote my own LDAP functionality. Please note: I use mongoid, and as such the code below is directed towards MongoDB. It can be easily modified for ActiveRecord, however.
In order to do this, I edited the new action in the sessions controller, similarly to as follows:
ldap = Net::LDAP.new
ldap.host = 'domainOrIP'
ldap.port = 389
ldap.auth 'user', 'password'
if ldap.bind
# success, so let's check if the user exists
#existing_user = User.where({username: params[:user][:username] }).first
if #existing_user == nil
#create the user
#user = User.new( {username: params[:user][:username], password: ''})
# I didn't personally store the user's password, as I use LDAP for authentication. (If you save this, please hash and salt it first!!)
#user.save
flash[:notice] = "Success!"
redirect_to '/'
else # already existed
#user = User.find({ username: params[:user][:username] })
flash[:notice] = "Success!"
redirect_to '/'
end
else
flash[:danger] = "An error occurred whilst authenticating with your LDAP server. Please check the configuration and try again."
redirect_to '/'
end
I then left Devise to handle everything else. Worked brilliantly for me -- the code above is from memory, however, so may not be 100% accurate. :)
edit 2:
More information on how to use the Net::LDAP class can be found here:
http://www.rubydoc.info/gems/ruby-net-ldap/Net/LDAP

Zendesk Single Sign-on gem for rails 3

Does anyone know of a maintained gem that handles user authentication for the Zendesk API through an existing Rails 3 application?
I asked Zendesk IT and got sent to https://github.com/tobias/zendesk_remote_auth, but it does not look rails 3 compatible and has not been updated since 2009.
I think the article in our docs gives the impression that Zendesk SSO is difficult when in fact it is pretty easy (http://www.zendesk.com/api/remote-authentication).
# reference http://www.zendesk.com/api/remote-authentication
# you need to be a Zendesk account admin to enable remote auth (if you have not already)
# go to Settings > Security, click "Enabled" next to Single Sign-On
# three important things to pay attention to:
# Remote Login URL, Remote Logout URL, and shared secret token
# for testing on a Rails 3 application running on localhost, fill in the Remote Login URL to map
# to http://localhost:3000/zendesk/login (we will need to make sure routes for that exist)
# fill in Remote Logout URL to http://localhost:3000/zendesk/logout
# copy the secret token, you'll need it later
# first, let's create those routes in config/routes.rb
namespace :zendesk do
match "/login" => "zendesk#login" # will match /zendesk/login
match "/logout" => "zendesk#logout" # will match /zendesk/logout
end
# Above I've mapped those requests to a controller named "zendesk" but it can be named anything
# next we want to add our secret token to the application, I added this in an initializer
# config/initializers/zendesk_auth.rb
ZENDESK_REMOTE_AUTH_TOKEN = "< your token >"
ZENDESK_REMOTE_AUTH_URL = "http://yourcompany.zendesk.com/access/remote/"
# Assuming we have a controller called zendesk, in zendesk_controller.rb
require "digest/md5"
class ZendeskController < ApplicationController
def index
#zendesk_remote_auth_url = ZENDESK_REMOTE_AUTH_URL
end
def login
timestamp = params[:timestamp] || Time.now.utc.to_i
# hard coded for example purposes
# really you would want to do something like current_user.name and current_user.email
# and you'd probably want this in a helper to hide all this implementation from the controller
string = "First Last" + "first.last#gmail.com" + ZENDESK_REMOTE_AUTH_TOKEN + timestamp.to_s
hash = Digest::MD5.hexdigest(string)
#zendesk_remote_auth_url = "http://yourcompany.zendesk.com/access/remote/?name=First%20Last&email=first.last#gmail.com&timestamp=#{timestamp}&hash=#{hash}"
redirect_to #zendesk_remote_auth_url
end
def logout
flash[:notice] = params[:message]
end
end
# Note that the above index action defines an instance variable #zendesk_remote_auth_url
# in my example I simple put a link on the corresponding view that hits ZENDESK_REMOTE_AUTH_URL, doing so
# will cause Zendesk to hit your applications Remote Login URL (you defined in your Zendesk SSO settings) and pass a timestamp back in the URL parameters
# BUT, it is entirely possible to avoid this extra step if you just want to go to /zendesk/login in your app
# notice I am either using a params[:timestamp] if one exists or creating a new timestamp with Time.now
This example is quite simplistic but I just want to illustrate the basic mechanics of Zendesk SSO. Note that I'm not touching the more complicated issue of creating new users or editing existing ones, just logging in users who have an existing Zendesk account.
There is an updated example code from zendesk
# Using JWT from Ruby is straight forward. The below example expects you to have `jwt`
# in your Gemfile, you can read more about that gem at https://github.com/progrium/ruby-jwt.
# Assuming that you've set your shared secret and Zendesk subdomain in the environment, you
# can use Zendesk SSO from your controller like this example.
class ZendeskSessionController < ApplicationController
# Configuration
ZENDESK_SHARED_SECRET = ENV["ZENDESK_SHARED_SECRET"]
ZENDESK_SUBDOMAIN = ENV["ZENDESK_SUBDOMAIN"]
def create
if user = User.authenticate(params[:login], params[:password])
# If the submitted credentials pass, then log user into Zendesk
sign_into_zendesk(user)
else
render :new, :notice => "Invalid credentials"
end
end
private
def sign_into_zendesk(user)
# This is the meat of the business, set up the parameters you wish
# to forward to Zendesk. All parameters are documented in this page.
iat = Time.now.to_i
jti = "#{iat}/#{rand(36**64).to_s(36)}"
payload = JWT.encode({
:iat => iat, # Seconds since epoch, determine when this token is stale
:jti => jti, # Unique token id, helps prevent replay attacks
:name => user.name,
:email => user.email,
}, ZENDESK_SHARED_SECRET)
redirect_to zendesk_sso_url(payload)
end
def zendesk_sso_url(payload)
"https://#{ZENDESK_SUBDOMAIN}.zendesk.com/access/jwt?jwt=#{payload}"
end
end

Advice on HTTP authentication scheme (using request headers)

I have a rails app hosted on Heroku that am restricting access to by using a proxy service. The external server acts as intermediary for all requests and handles user authentication. Once a user has authenticated, the server (I think LDAP) adds the user name to the request header and redirects them to my app.
I would like to use the username from the request header to authenticate users in my app. Basically if the user doesn't exist I would create a user with that username (no password required) and if not I would just log them in. I will be storing the users in my app's database.
How should I do this? Is it possible to use Devise for this purpose?
Edit: I got it working with Devise/custom Warden strategy like this:
# config/initializers/my_strategy.rb
Warden::Strategies.add(:my_strategy) do
def valid?
true
end
def authenticate!
if !request.headers["my_key"]
fail!("You are not authorized to view this site.")
redirect!("proxy_url")
else
username = request.headers["my_key"]
user = User.find_by_username(username)
if user.nil?
user = User.create(:username => username)
end
success!(user)
end
end
end
#config/initializers/devise.rb
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :my_strategy
end
I need to make this as bullet proof as possible. Are there other security measures can I take to make sure someone can't spoof the request header and access my site?
I think using devise can be a little more overkill, but you can. You just need define a warden strategie. in devise or use only warden in this purpose.

Resources