Multiple models with devise + omniauth in Rails - ruby-on-rails

I am using devise + omniauth to enable facebook/linkedin/twitter login on my website.
I have multiple models with totally different roles and both need social logins, I have been using devise+omniauth successfully unitl now as I needed that only for one login.
However now devise gives error saying I cant use omniauthable on multiple models.
I looked around and someone was helpful enough to create this wiki explaining how to use omniauth with devise without omniauthable module.
But I think since I would want to use it for different models, I would have to use different paths for signup, which this wiki doesnt explain.
I looked into omniauth and it has some scarce details about options request_path and callback_path which seem to be relevant, but I couldn't figure out what exactly needs to be done.
Any help will be much appreciated.
EDIT
This is what I have done till now.
# omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
FACEBOOK_AUTH_REGEX = /^\/(model1|model2)\/auth\/facebook\/$/
FACEBOOK_CALLBACK_REGEX = /^\/(model1|model2)\/auth\/facebook\/callback\/$/
LINKEDIN_AUTH_REGEX = /^\/(model1|model2)\/auth\/linkedin\/$/
LINKEDIN_CALLBACK_REGEX = /^\/(model1|model2)\/auth\/linkedin\/callback\/$/
facebook_callback_path = lambda do |env|
env["PATH_INFO"] =~ FACEBOOK_CALLBACK_REGEX
end
linkedin_callback_path = lambda do |env|
env["PATH_INFO"] =~ LINKEDIN_CALLBACK_REGEX
end
facebook_request_path = lambda do |env|
match_data = FACEBOOK_AUTH_REGEX.match env["PATH_INFO"]
if match_data
"/#{match_data[1]}/auth/facebook/callback/"
end
end
linkedin_request_path = lambda do |env|
match_data = LINKEDIN_AUTH_REGEX.match env["PATH_INFO"]
if match_data
"/#{match_data[1]}/auth/linkedin/callback/"
end
end
provider :facebook, FACEBOOK_ID, FACEBOOK_SECRET, :callback_path => facebook_callback_path, :request_path => facebook_request_path
provider :linkedin, LINKEDIN_ID, LINKEDIN_SECRET, :callback_path => linkedin_callback_path, :request_path => linkedin_request_path
end
#routes.rb
match "/:model_name/auth/:provider/callback/", :to => "recruiter_omniauth_callbacks#sync"
And it seems to work fine for linkedin but in facebook I get
{"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100}}
Not Sure why I get this, what are the two urls it says should be same, I think I am using :model_name/auth/facebook/callback/ everywhere so not sure what is the problem it has.
Any help?

I post an example for a similar case here with other perspective of attacking that problem, hope it helps, regards!

Related

Send Dynamic params with omniauth-saml

I need a way to send dynamic params using omniauth-saml from SP TO IDP. The requirement is there are 2 websites website 1 and website 2. Website 1 is controlled by another team where saml is already implemented. On my website, I have added a button and on click of it, I will send a request to website 1. Along with the request I need to send user parameters such as first_name, last_name, email & some custom attributes. In my previous stackoverflow post I was able to understand that I need to make use of omniauth-saml and some basic details. But the issue which I am still not able to send dynamic attributes.
When I am going through the documentation I believe I need to make use of
:idp_sso_target_url_runtime_params => {:original_request_param => :mapped_idp_param},
But I am not sure how can I pass dynamic params through it. In my previous post, a person referred me to do a monkey patch but it didn't work for me. Could anyone has any suggestion
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
#:assertion_consumer_service_url => "consumer_service_url",
:issuer => "my_application",
:idp_sso_target_url => "target_url",
:idp_sso_target_url_runtime_params => {:original_request_param => :mapped_idp_param},
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
end
You can pass dynamic values via middleware adapter.
Like:
app/service/saml_idp_setting_adapter.rb
class SamlIdpSettingAdapter
def self.settings(issuer)
idp = ::IdentityProvider.find_by_issuer(issuer)
if idp.present?
{
assertion_consumer_service_url: "#{ENV['APP_URL']}/users/saml/auth",
assertion_consumer_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
issuer: "issuer",
idp_entity_id: idp.entity_id,
idp_slo_service_url: idp.slo_target_url,
idp_sso_service_url: idp.sso_target_url,
idp_cert_fingerprint: idp.cert_fingerprint,
idp_cert_fingerprint_algorithm: 'http://www.w3.org/2000/09/xmldsig#sha256'
}
else
{}
end
end
end
and setup initialiser file with above adaptor
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
:idp_settings_adapter => SamlIdpSettingAdapter
end

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

With Omniauth, How to record all requests for authentication?

with omniauth in my app, to have a user use Google oAuth2 to authenticate I redirect the user to:
/users/auth/google_oauth2
If the users approves the request, then the AuthenticationsController#create is called.
With AuthenticationsController#create - I can add event tracking to record the # of users who approve google auth. What I don't have is the number that I sent to approve meaning I don't have a conversion rate.
How can I track the # of people who hit the URL around making requests to connect.
A nasty solution would be to build a filter around the method Strategy#request_call and do the tracking there.
Inside an initializer:
OmniAuth::Strategy.class_eval do
def request_call_with_tracking
log :info, "Im running before the actual request_call"
Tracker.hit(name) #name will return the provider
request_call_without_tracking
end
alias_method_chain :request_call, :tracking
end
You can achieve this by using the OmniAuth setup phase. You can pass a :setup option to an OmniAuth provider, with a proc which will be executed before the authentication is performed. You can add event tracking inside this proc.
So if you have some tracker class, you can do this:
use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'],
:setup => lambda { |env|
Tracker.track
}
end
For more information check out Avdi Grimm's great blog post about the subject.

Rails custom consumer oauth strategy

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?

override "/auth/identity"-page of omniauth identity

I'm using omniauth without devise for authentication, as I like it's simplicity. In addition to omniauth-facebook I use omniauth-identity to offer email/pw-authentication.
The railscast on omniauth-identity describes how to setup a customized registration and login page. But the default routes supplied by identity (/auth/identity and /auth/identity/register) are still accessible.
I would like to have these under my control, as I want only want to let invited users register. Is there any way to override those routes supplied by a rack middleware?
Trying to just
match "/auth/identity", to: "somewhere#else"
doesn't do the trick!
Is there maybe a configuration to turn these default routes off? The documentation isn't giving any details on this...
Unfortunately I'm fairly new to Rack, so I don't have enough insight yet, to solve this issue on my own!
I'd be glad, if someone could point me in the right direction!
An OmniAuth strategy object has a method request_phase which generates a html form and shows it to user. For "omniauth-identity" strategy this would be the form you see at /auth/identity url.
You can override the request_phase method and replace the form generator with, for example, a redirect to your custom login page (assuming you have it available at /login url). Place the following along with your omniauth initialization code:
module OmniAuth
module Strategies
class Identity
def request_phase
redirect '/login'
end
end
end
end
# Your OmniAuth::Builder configuration goes here...
In addition to 1gors and iains answer:
"/auth/identity/register" is served with GET as well, to override, I had to:
class OmniAuth::Strategies::Identity
alias :original_other_phase :other_phase
def other_phase
if on_registration_path? && request.get?
redirect '/sign_up'
else
original_other_phase
end
end
end
You can set method in omniauth.rb
:on_login => SessionsController.action(:new)
for example:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :identity,
:fields => [:nickname],
:on_login => SessionsController.action(:new),
:on_registration => UsersController.action(:new),
:on_failed_registration => SessionsController.action(:registration_failure)
end

Resources