I have a Rails 5 site which consists of 2 parts:
Admin area
API-only client area
I'm using Devise for both parts and https://github.com/lynndylanhurley/devise_token_auth gem for the API frontend.
The problem is about using the omniauth authentication. When I omniauth authenticate into the admin area - everything is ok - I get back some successful HTML-response.
But the problem is that I'm getting the same HTML-response in the API-area - but I need some JSON-response - not HTML one.
Here is my code:
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }
namespace :api do
mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions', :omniauth_callbacks => 'api/users/omniauth_callbacks' }
end
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules.
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable,
:omniauth_providers => [:facebook, :vkontakte]
include DeviseTokenAuth::Concerns::User
devise :omniauthable
def self.from_omniauth_vkontakte(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.extra.raw_info.first_name.to_s + "." + auth.extra.raw_info.last_name.to_s + '#vk.com'
user.password = Devise.friendly_token[0,20]
end
end
end
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def vkontakte
#user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"], provider_ignores_state: true
config.omniauth :vkontakte, ENV["VKONTAKTE_APP_ID"], ENV["VKONTAKTE_APP_SECRET"]
end
Gemfile
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-vkontakte'
Gemfile.lock
devise (4.3.0)
devise_token_auth (0.1.42)
Here's my log:
Started GET "/api/auth/vkontakte" for 127.0.0.1 at 2017-06-20 17:34:23
+0300
Started GET "/omniauth/vkontakte?namespace_name=api&resource_class=User" for
127.0.0.1 at 2017-06-20 17:34:23 +0300
I, [2017-06-20T17:34:23.237270 #15747] INFO -- omniauth: (vkontakte) Request phase initiated.
Started GET "/omniauth/vkontakte/callback?code=0b8446c5fe6873bb12&state=52254649eb899e3b743779a1a4afc0304f249a6dd90b4415" for 127.0.0.1 at 2017-06-20 17:34:23 +0300
I, [2017-06-20T17:34:23.672200 #15747] INFO -- omniauth: (vkontakte) Callback phase initiated. Processing by Users::OmniauthCallbacksController#vkontakte as */* Parameters: {"code"=>"0b8446c5fe6873bb12", "state"=>"52254649eb899e3b743779a1a4afc0304f249a6dd90b4415"}
I guess that the problem is about a so-called "callback" url. I don't understand where it is set. It is obvious from the log that at the end of the auth process the GET "/omniauth/vkontakte/callback..." query is called. And probably it is called always - no matter if I initiated the oath sequence from admin or api client area.
I use Chrome Postman to make the API query http://localhost:3000/api/auth/vkontakte - and I get the HTML-response back ("successful login etc.") - but I need surely some JSON-response.
Is there a way to dynamically change the callback path depending on some precondition?
Is the callback query somewhat different depending on from where the oath procedure was initiated?
EDIT1:
This is not a single problem here unfortunately. Looks like the oauth is simply not implemented in the https://github.com/lynndylanhurley/devise_token_auth gem. So, even if I succeed to switch the oauth login procedure to the JSON way - how do I login the user the devise_token_auth-way - generating 3 tokens etc...? The app/controllers/users/omniauth_callbacks_controller.rb needs to be totally reimlemented.
You can render json from your OmniauthCallbacksController based on some extra parameter provided when your request a connection from the API for example.
These extra parameters will be availables in this hash request.env["omniauth.params"].
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def vkontakte
#user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
if #user.persisted?
sign_in #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
if request.env["omniauth.params"]["apiRequest"]
render status: 200, json: { message: "Login success" }
else
redirect_to after_sign_in_path_for(#user)
end
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
You can this extra parameters by calling the auth helper with additional parameters, they will be passed to your OmniauthController : user_vkontakte_omniauth_authorize_path(api_request: true) (Or whatever your route helper is)
I ended up implementing my own oauth callback procedure - instead of using one from the devise_token_auth gem.
The devise_token_auth gem does contain the oauth authentication - but it appears to be not working properly.
Here are my code changes:
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }
namespace :api do
mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions'}
end
end
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
include DeviseTokenAuth::Concerns::SetUserByToken
def vkontakte
#user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
namespace_name = request.env["omniauth.params"]["namespace_name"]
if #user.persisted?
if namespace_name && namespace_name == "api"
#client_id = SecureRandom.urlsafe_base64(nil, false)
#token = SecureRandom.urlsafe_base64(nil, false)
#user.tokens[#client_id] = {
token: BCrypt::Password.create(#token),
expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
}
#user.save
#resource = #user # trade-off for "update_auth_header" defined in "DeviseTokenAuth::Concerns::SetUserByToken"
sign_in(:user, #user, store: false, bypass: false)
render json: #user
else
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
end
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
The inclusion of include DeviseTokenAuth::Concerns::SetUserByToken provides 5 auth headers in response:
access-token →BeX35KJfYVheKifFdwMPag
client →96a_7jXewCThas3mpe-NhA
expiry →1499340863
token-type →Bearer
uid →376449571
But the response still lacks these headers (available at a common sign-in):
Access-Control-Allow-Credentials →true
Access-Control-Allow-Methods →GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin →chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
Access-Control-Max-Age →1728000
I don't know whether they are important and if yes - how to provide them.
PS The same identical approach works with Facebook too.
Related
OK i'm not really a ROR person but I'm trying to modify an existing app (forked) from https://github.com/adamcooke/staytus
there's an /admin page which right now brings you to a haml login form with a username/password box.
What I'm attempting to do us change this functionally so that when /admin is hit devise and omniAuth will redirect to my IDP via OpenIDConnect ask the user to login with their creds, do the auth stuff and pending they get through then show the admin section...
here's what I've done thus far:
installed these gems
gem 'devise'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-azure-activedirectory-v2'
ran the devise config/install:
added config.rb
def omniauth_oidc?
result = ENV['OMNIAUTH_OIDC'] == '1'
puts "omniauth_oidc? result: #{result}"
I've tried all combinations of routes:
devise_scope :user do
devise_for :users, controllers: { omniauth_callbacks: 'admin/omniauth_callbacks' }
end
devise_scope :user do
get '/admin/omniauth_callbacks' => 'admin/omniauth_callbacks#azure_activedirectory_v2'
end
namespace :admin do
get '/omniauth_callbacks', to: 'omniauth_callbacks#azure_activedirectory_v2'
end
result
end
I've also tried " to: 'sessions#create'" routes but clearly I'm missing something here...
added OmniauthCallbacksControler
class Admin::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def azure_activedirectory_v2
puts "request.env['omniauth.auth']: #{request.env['omniauth.auth'].inspect}"
response_params = request.env['omniauth.auth']
if response_params.nil?
Rails.logger.error("request.env['omniauth.auth'] is nil")
raise "request.env['omniauth.auth'] is nil"
else
response_params = response_params['info']
end
#user = User.find_by!(email: response_params['email'])
if #user&.persisted?
sign_in_and_redirect #user, event: :authentication, location: admin_root_path
else
flash[:danger] = 'You have not yet an account!'
redirect_back(fallback_location: admin_root_path)
end
end
end
added omniauth.rb initializer
Rails.application.config.middleware.use OmniAuth::Builder do
provider :developer if Rails.env.development?
provider :azure_activedirectory_v2,
{
client_id: ENV[''],
client_secret: ENV[''],
tenant_id: ENV[']
}
end
given all of the above I still haven't gotten /admin to redirect to my IDP login ? WHY ?
So I'm trying to configure Rails with Google oauth via devise, i have followed official docs described here. After everything i get this error when clicking the google signup button
Access to fetch at 'url' (redirected from 'http://localhost:3000/users/auth/google_oauth2') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Googling around i found you need to enable CORS, naturally i did just that
in my application.rb in added this
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :patch, :put]
end
end
This is my callbacks controller
class Users::CallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token
def google_oauth2
admin = User.from_omniauth(from_google_params)
redirect_to root_path
end
private
def from_google_params
#from_google_params ||= {
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image,
is_member: false
}
end
def auth
#auth ||= request.env['omniauth.auth']
end
end
EDIT
So after alot of trial n errors i have it working, kinda. So when i click the signup with google it still throws an error,
But, when clicking the link in the google console it successfully goes there, any ideas ?
Try this setup with devise at master branch and gem omniauth-rails_csrf_protection
devise.rb
config.omniauth :google_oauth2, "API_KEY", "API_SECRET"
routes.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
user.rb
devise :omniauthable, omniauth_providers: [:google_oauth2]
app/controllers/users/omniauth_callbacks_controller.rb:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.from_omniauth(request.env['omniauth.auth'])
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'].except('extra') # Removing extra as it can overflow some session stores
redirect_to new_user_registration_url, alert: #user.errors.full_messages.join("\n")
end
end
end
user.rb
def self.from_omniauth(access_token)
data = access_token.info
user = User.where(email: data['email']).first
unless user
user = User.create(
email: data['email'],
password: Devise.friendly_token[0,20])
end
user
end
gemfile:
gem "devise", github: "heartcombo/devise", branch: "master"
gem "omniauth-rails_csrf_protection"
gem "omniauth-google-oauth2"
view:
<%= link_to "Sign in with Google", user_omniauth_authorize_path(:google_oauth2), method: :post %>
I'm using the omniauth-facebook gem with devise. It was working until recently. I also recently upgrated to Rails 5.0.1 from Rails 4, but I'm not sure that's the cause.
I currently have 0 users, and I'm logged into Facebook. But when I try to sign up for my app with Facebook on localhost, I get this error:
NoMethodError in RegistrationsController#facebook
undefined method `provider' for nil:NilClass
Here is my User model. I marked the line that the error highlights.
User.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| #ERROR
#data = auth.info
user.name = #data.name
# ...
end
end
RegistrationsController
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
Also, here's my link:
<%= link_to "fb", user_facebook_omniauth_callback_path(:facebook, thing: #thing.id, degree: #degree, :format => :js) %>
The HTML Output:
<a href=\"/auth/facebook/callback.js?thing=2\">fb<\/a>
And the path:
localhost:3000/auth/facebook/callback.js?thing=2
So the problem is that request.env["omniauth.auth"] is nil for some reason. I can't find any traces of similar errors in any documentation.
Anyone encounter this before or have any thoughts?
To authenticate via facebook all you need is to put a link from your Site to facebook like this:
www.yoursite.com/auth/facebook
and then set up a route to receive the callback from Facebook with the authentication hash:
#routes.rb
get 'auth/facebook/callback' => 'sessions#create_facebook'
Can you specify how the output of this line looks like or why you are passing other information ?:
<%= link_to "fb", user_facebook_omniauth_callback_path(:facebook, thing: #thing.id, degree: #degree, :format => :js) %>
EDIT
auth/facebook/callback is a get request. Facebook sends you the users authentication hash there. Only facebook itself should use that route. When you want to authenticate your link has to be:
localhost:3000/auth/facebook
They way you have it, omniauth is expecting facebook's authentication hash but receives "?thing=2" which results in a failed authentication. Omniauth tries to extract the information from "?thing=2" which is not a hash and when you try to access auth[provider], auth is empty and therefore provider is not defined either which results in :
undefined method `provider' for nil:NilClass
I had the same issue and solved it by removing :omniauthable from User model
I have a problem with omniauth and devise_token_auth. When i try to register an user with facebook, the callback not working.
Her's my Gemfile :
gem 'rack-cors', :require => 'rack/cors'
gem 'devise_token_auth'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'devise', '~> 3.2'
Routes.rb
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth', :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match 'auth/:provider/callback', to: 'sessions#create', via: [:get, :post]
end
end
root to:'welcomes#index'
end
Here's the app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, "XXX", "XXX",
:scope => 'public_profile,email,user_birthday',
:info_fields => 'id,about,birthday,email,first_name,gender,last_name'
end
And finally, this is the console response :
Started GET "/api/v1/auth/facebook" for 192.168.33.1 at 2016-05-14 10:26:00 +0000
Cannot render console from 192.168.33.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Started GET "/omniauth/facebook?resource_class=User" for 192.168.33.1 at 2016-05-14 10:26:00 +0000
Cannot render console from 192.168.33.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
I, [2016-05-14T10:26:00.995076 #3226] INFO -- omniauth: (facebook) Request phase initiated.
Started GET "/omniauth/facebook/callback?code=XXX" for 192.168.33.1 at 2016-05-14 10:26:01 +0000
Cannot render console from 192.168.33.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
I, [2016-05-14T10:26:01.130357 #3226] INFO -- omniauth: (facebook) Callback phase initiated.
AbstractController::ActionNotFound (The action 'redirect_callbacks' could not be found for Users::OmniauthCallbacksController):
Am I missing something simple? I've been searching for a solution for the last few day
The problem is in your OmniauthCallbacksController.
One way is to inherit the controller from DeviseTokenAuth::OmniauthCallbacksController and hooked into the actions:
class Users::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
def redirect_callbacks
super
# some logic here
end
end
Other way is to copy the whole controller code from the devise_token_auth gem and work around them:
module DeviseTokenAuth
class OmniauthCallbacksController < DeviseTokenAuth::ApplicationController
attr_reader :auth_params
skip_before_action :set_user_by_token, raise: false
skip_after_action :update_auth_header
# intermediary route for successful omniauth authentication. omniauth does
# not support multiple models, so we must resort to this terrible hack.
def redirect_callbacks
# derive target redirect route from 'resource_class' param, which was set
# before authentication.
devise_mapping = [request.env['omniauth.params']['namespace_name'],
request.env['omniauth.params']['resource_class'].underscore.gsub('/', '_')].compact.join('_')
redirect_route = "#{request.protocol}#{request.host_with_port}/#{Devise.mappings[devise_mapping.to_sym].fullpath}/#{params[:provider]}/callback"
# preserve omniauth info for success route. ignore 'extra' in twitter
# auth response to avoid CookieOverflow.
session['dta.omniauth.auth'] = request.env['omniauth.auth'].except('extra')
session['dta.omniauth.params'] = request.env['omniauth.params']
redirect_to redirect_route
end
# some code here
end
end
EDIT: Resolved this issue. Ended up being trivial; I forgot to restart the Rails server after editing my config file.
I'm trying to integrate Stripe OAuth in a Rails application w/ Devise using this tutorial. I think I've followed it to a t, but I'm receiving this error when I go to Connect to Stripe on my app.
{"error":{"message":"No application matches the supplied client
identifier"}}
I'm not sure how to check whether the client identifier I declare in my application.yml is even being passed in, or what value it's reading. Here's my setup so far:
config/application.yml (has my IDs from Stripe account - I edited them out here):
STRIPE_CONNECT_CLIENT_ID: "____________"
STRIPE_SECRET_KEY: "_____________"
config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :stripe_connect,
ENV['STRIPE_CONNECT_CLIENT_ID'],
ENV['STRIPE_SECRET_KEY'],
:scope => 'read_write',
:stripe_landing => 'register'
*other config code*
end
config/routes.rb
Studiocracy::Application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "omniauth_callbacks", registrations: 'registrations' }
*other stuff*
end
controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def stripe_connect
#user = current_user
if #user.update_attributes({
provider: request.env["omniauth.auth"].provider,
uid: request.env["omniauth.auth"].uid,
access_code: request.env["omniauth.auth"].credentials.token,
publishable_key: request.env["omniauth.auth"].info.stripe_publishable_key
})
# anything else you need to do in response..
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Stripe") if is_navigational_format?
else
session["devise.stripe_connect_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
app/views/devise/registrations/edit.html.erb
<%= link_to image_tag('stripe.png'), user_omniauth_authorize_path(:stripe_connect) %>
I did the same silly mistake. Later i found out, it is because of wrong client id i am passing. Just chech href tag of your connect to stripe button once and recheck.
I got a solution by adding just a foward slash (/) after the main URL before the question mark (?):
Before:
https://connect.stripe.com/oauth/v2/authorize?scope=read_write&client_id={client_id}&redirect_uri={https//:example.com}
v
https://connect.stripe.com/oauth/v2/authorize/?scope=read_write&client_id={client_id}&redirect_uri={https//:example.com}
^