Rails3 omniauth google authentication on Returns User Identity - ruby

My Omniauth integration works on local development but fails for google on staging.
require 'omniauth/openid'
require 'openid/store/memcache'
Rails.application.config.middleware.use OmniAuth::Builder do
OmniAuth.config.full_host = "http://xx.xx.xxx/"
# dedicated openid
provider :open_id, OpenID::Store::Memcache.new(Dalli::Client.new), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id'
end
I get a this error message:
Started GET "/auth/failure?message=invalid_credentials" for 58.71.19.178 at 2011-12-01 02:22:20 +0000
Processing by ErrorsController#routing as HTML
Parameters: {"message"=>"invalid_credentials", "a"=>"auth/failure"}
Rendered public/404.html (0.1ms)
Completed 404 Not Found in 1ms (Views: 0.6ms | ActiveRecord: 0.0ms)
Also the ip in for is not the same in my OmniAuth.config.full_host maybe that could be causing the issue?

The culprit was that apache sending and returning on different ips
This monkey patch fixed the issue.
module OmniAuth
module Strategies
# OmniAuth strategy for connecting via OpenID. This allows for connection
# to a wide variety of sites, some of which are listed [on the OpenID website](http://openid.net/get-an-openid/).
class OpenID
protected
def callback_url
uri = URI.parse(request.url)
uri.path += '/callback'
# by KirylP: to overcome hosting subdomain forwarding to rails port
uri.port = '' if request.env.has_key? 'HTTP_X_FORWARDED_SERVER'
uri.to_s
end
end
end
end
module Rack
class OpenID
SERVER_PORT_TO_AVOID = 12002
private
def realm_url(req)
url = req.scheme + "://"
url << req.host
scheme, port = req.scheme, req.port
if scheme == "https" && port != 443 ||
scheme == "http" && port != 80
url << ":#{port}" if port != SERVER_PORT_TO_AVOID # KirylP
end
url
end
end
end
module OpenID
class Consumer
def complete(query, current_url)
message = Message.from_post_args(query)
current_url.sub!(":#{Rack::OpenID::SERVER_PORT_TO_AVOID}", '') # KirylP
mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
begin
meth = method('complete_' + mode)
rescue NameError
meth = method(:complete_invalid)
end
response = meth.call(message, current_url)
cleanup_last_requested_endpoint
if [SUCCESS, CANCEL].member?(response.status)
cleanup_session
end
return response
end
end
end

I had a similar problem. Seems like your google authentication fails (can be for different reasons - invalid credentials, or user denied access), therefore you receive callback to /auth/failure -- and then you get 404.
Did you implement a route for /auth/failure in your routes.rb? In my current project:
in routes.rb
match '/auth/failure', :to => 'sessions#failure'
in sessions_controller
def failure
redirect_to session[:return_uri] || root_path, alert: "Sorry, we were not able to authenticate you using your chosen sign on method"
end

Related

How to fix the problem, When I try authentication on local system its working perfectly, but when uploaded to heroku it comes back with error 500?

I am new to rails and react, this might be a simple one but i cant seem to figure it out.
I am trying to implement a simple jwt authentication using ruby on rails with react as client. I followed the steps that was suggested in :
https://www.pluralsight.com/guides/token-based-authentication-with-ruby-on-rails-5-api
It works as expected on my local system but when i uploaded my app on to heroku it always comes back with error : 500. All the other 'Post' and 'Get' requests work normally. Its only when i try to authenticate and get the auth_token back it runs into 500 error.
this is the request format
post: localhost:3001/api/authenticate
and body:
{
"email": "evin#xyz.com",
"password": "evin"
}
I verified that this data is available on heroku by using get which works perfectly.
I have been working on resolving this for over 2 days now. There is very little information available online on this authentication. There was plenty of recommendations on using auth0. But i could not find much help with this form of authentication.
This is what i have
#Path: /app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
#Path: app/controllers/api/authentication_controller.rb
class Api::AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :unauthorized
end
end
end
#Path: /app/commands/authenticate_user.rb
class AuthenticateUser
prepend SimpleCommand
def initialize(email, password)
#email = email
#password = password
end
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_accessor :email, :password
def user
user = User.find_by_email(email)
return user if user && user.authenticate(password)
errors.add :user_authentication, 'invalid credentials'
nil
end
end
#Path: /app/commands/authorize_api_request.rb
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
#headers = headers
end
def call
user
end
private
attr_reader :headers
def user
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
#user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
#decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
nil
end
end
#Path: /lib/json_web_token.rb
class JsonWebToken
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def decode(token)
body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
HashWithIndifferentAccess.new body
rescue
nil
end
end
end
#path: /config/application.rb
require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Deveycon
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
#Autoload lib for encrypt and decrypt
config.autoload_paths << Rails.root.join('lib')
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
end
end
I had similar issues, the API works perfectly on localhost after uploading to Heroku, I still got unauthorized on secure pages even with the token on the headers.
I added
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
to config/secrets.yml
Please check the more details log of your heroku application by using Heroku CLI.
heroku logs -t
If the problem with AuthenticateUser::JsonWebToken use auto loaded in your
config/application.rb
class Application < Rails::Application
#.....
config.autoload_paths << Rails.root.join('lib')
#.....
end
I hope that helpful to resolve your issue.
In #lib/JsonWebToken:
Just increase the exp time of token and replace .secrets.secret_key_base with
.credentials.read
class JsonWebToken
class << self
def encode(payload, exp = 1200.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, Rails.application.credentials.read)
end
def decode(token)
body = JWT.decode(token, Rails.application.credentials.read)[0]
HashWithIndifferentAccess.new body
rescue
nil
end
end
end

Google API Server-to-Server Communication not working (Ruby implementation)

I have followed all the steps to set up a Domain-Wide Delegation of Authority (https://developers.google.com/admin-sdk/reports/v1/guides/delegation) and followed this link too (https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority) and I ended up creating this class in Ruby.
class Gsuite::ServiceAccount
def initialize(person: nil)
end
def authorized_client
Google::Auth::ServiceAccountCredentials.make_creds(
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:gsuite_service_account]
[:credentials_file]),
scope: AppConfig[:gsuite_service_account][:scope])
client = authorizer.fetch_access_token!
end
end
This class returns me this hash
{"access_token"=>"a_long_token_string_here", "expires_in"=>3600, "token_type"=>"Bearer"}
Then, I've created this method (within my Admin class) to connect to Gmail Service
def gsuite_client_access
#client ||= Gsuite::ServiceAccount.new(person: self.email.to_s).authorized_client
authorized_client = Google::Apis::GmailV1::GmailService.new
authorized_client.authorization = #client
authorized_client
end
So, when I try to list my Gmail Messages with this line in another part of the code
inbox = current_admin.gsuite_client_access.list_user_messages('me', max_results: 10)
I get the following error message =>
Sending HTTP get https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10
401
#<Hurley::Response GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10 == 401 (238 bytes) 645ms>
Caught error Unauthorized
Error - #<Google::Apis::AuthorizationError: Unauthorized>
Retrying after authentication failure
Google::Apis::AuthorizationError: Unauthorized
Any ideas what's missing here?
Finally, I got it working. Turns out, you need to use this line to use the sub method to the "impersonated user" to be able to connect.
authorizer.sub = #person
And, for your delight, here is the updated test code for reading Gmail messages so you can follow in case you want to use it. Just remember to save the credentials.json file in your project folder to make it work and use the same scope you added in the GSuite Dashboard.
class Gsuite::ServiceAccount
def initialize(person: nil)
#person = person
end
def read_messages
client = service_account_access
inbox = client.list_user_messages(#person, max_results: 5, label_ids: "INBOX" )
if inbox.result_size_estimate.nonzero?
inbox.messages.each do |message|
response = client.get_user_message(#person, message.id)
end
end
end
private
def service_account_access
token = authorized_client
client = Signet::OAuth2::Client.new(access_token: token['access_token'])
client.expires_in = Time.current + token["expires_in"]
auth_client = Google::Apis::GmailV1::GmailService.new
auth_client.authorization = client
auth_client
end
def authorized_client
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:credentials_file]),
scope: AppConfig[:gsuite_scope]).dup
authorizer.sub = #person
authorizer.fetch_access_token!
end
end

Google API Client secrets failing to load issued_at

I'm getting the following error message when I try to do an OAuth2 connection to google.
.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/google-api-client-0.7.1/lib/google/api_client/auth/file_storage.rb:49:in `at': can't convert nil into an exact number (TypeError)
Looking at the source this is trying to read a cached credentials file and is failing to parse an attribute called issued_at.
I initially set up my app with the wrong port in the google developer console. Now I've updated the client_secrets.json but I'm continually getting this error.
My code is trying doing the calendar example from the google site, but converted to use the admin directory API, but it isn't getting beyond the auth step.
Where is this cached value coming from?
require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'sinatra'
require 'logger'
enable :sessions
CREDENTIAL_STORE_FILE = "client_secrets.json"
def logger; settings.logger end
def api_client; settings.api_client; end
def directory_api; settings.directory; end
def user_credentials
# Build a per-request oauth credential based on token stored in session
# which allows us to use a shared API client.
#authorization ||= (
auth = api_client.authorization.dup
auth.redirect_uri = to('/oauth2callback')
auth.update_token!(session)
auth
)
end
configure do
log_file = File.open('directory.log', 'a+')
log_file.sync = true
logger = Logger.new(log_file)
logger.level = Logger::DEBUG
client = Google::APIClient.new(
:application_name => 'Ruby Directory sample',
:application_version => '1.0.0')
puts "store file : #{CREDENTIAL_STORE_FILE}"
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
if file_storage.authorization.nil?
client_secrets = Google::APIClient::ClientSecrets.load
client.authorization = client_secrets.to_authorization
client.authorization.scope = 'https://www.googleapis.com/auth/admin.directory.user'
else
client.authorization = file_storage.authorization
end
# Since we're saving the API definition to the settings, we're only retrieving
# it once (on server start) and saving it between requests.
# If this is still an issue, you could serialize the object and load it on
# subsequent runs.
directory = client.discovered_api('admin', "directory_v1")
set :logger, logger
set :api_client, client
set :directory, directory
end
before do
# Ensure user has authorized the app
unless user_credentials.access_token || request.path_info =~ /\A\/oauth2/
redirect to('/oauth2authorize')
end
end
after do
# Serialize the access/refresh token to the session and credential store.
session[:access_token] = user_credentials.access_token
session[:refresh_token] = user_credentials.refresh_token
session[:expires_in] = user_credentials.expires_in
session[:issued_at] = user_credentials.issued_at
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
file_storage.write_credentials(user_credentials)
end
get '/oauth2authorize' do
# Request authorization
redirect user_credentials.authorization_uri.to_s, 303
end
get '/oauth2callback' do
# Exchange token
user_credentials.code = params[:code] if params[:code]
user_credentials.fetch_access_token!
redirect to('/')
end
get '/' do
result = api_client.execute(:api_method => directory.users.list)
# # Fetch list of events on the user's default calandar
# result = api_client.execute(:api_method => calendar_api.events.list,
# :parameters => {'calendarId' => 'primary'},
# :authorization => user_credentials)
[result.status, {'Content-Type' => 'application/json'}, result.data.to_json]
end
Change the line to CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json" and then rename the json you downloaded from the Google dashboard to client_secrets.json per convention. CREDENTIAL_STORE_FILE is where your OAuth tokens get stored and is created by the FileStorage instance.

OAuth2 gem: implementation for third party - access other accounts data in seek.com

I'm connecting to an API (seek.com.au) which uses OAuth2 for authentication. I struggled with OAuth2 gem for a while and I ended up writing the plain requests as will follow. Although this is working, I still would like to understand what was wrong with my initial OAuth2 implementation.
Here is my current working code, **the third party* relates to the fact that I'm accessing the API with an account that have access to other accounts. This logic is mainly implemented in the scope method (at the bottom of this snippet).
The following includes some extra logic, but the get_grant and post_for_token methods should include everything.
module Seek::Base
CONFIG = YAML.load_file "#{Rails.root}/config/seek.yml"
HOST = 'http://test.api.seek.com.au/v1/'
REQUEST_URIS = {
get_grant: HOST + 'OAuth/auth',
post_for_token: HOST + 'OAuth/token',
get_applications: HOST + 'advertiser/applications'
}
def uri_for(request, params = {})
uri = REQUEST_URIS[request]
uri += '?' + params.to_param if params.any?
URI.parse uri
end
end
class Seek::OAuth2 # TODO? is instance needed?
include Seek::Base
# by account_id
##tokens = {}
def initialize(account_id)
#account_id = account_id
end
def self.authenticate!(account_id)
new(account_id).authenticate!
end
# eg: when a request responded that the token is expired
def self.expire_token(account_id)
##tokens.delete account_id
end
###########################################################################
############################### begin #####################################
# authentication
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth-2.0
def authenticate!
##tokens[#account_id] ||= begin
grant = get_grant
raise Exception.new(#error) if #error
Rails.logger.info "Retrive token for #{#account_id}"
post_for_token
end
end
private
# part of t he authentication process
# as we have one account for many entities, we use third party variation
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth2/auth
def get_grant
uri = uri_for :get_grant, {response_type: :code, client_id: username, scope: scope}
response = Net::HTTP.get_response uri
params = response['location'].split('?').second
#error = params.split('error=').second
#grant_code = params.split('code=').second
end
# part of the authentication process
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth2/token
def post_for_token
uri = uri_for :post_for_token
request = Net::HTTP::Post.new uri.path, {'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'}
request.set_form grant_type: :authorization_code, code: #grant_code, redirect_uri: ''
request.basic_auth username, password
response = Net::HTTP.new(uri.host, uri.port).request request
JSON(response.body)['access_token']
end
########################## end ############################################
###########################################################################
def username
CONFIG['credentials']['username']
end
def password
CONFIG['credentials']['password']
end
############## the scope method
############## I think I need to insert this in the OAuth request
def scope
"urn:seek:thirdparty:username:#{username},urn:seek:advertiser:identity:#{#account_id}"
end
end
And here are the few lines (to replace the authenticate! method) that were meant to do the same, but sadly, OAuth returns invalid_client.
client = OAuth2::Client.new(username, password, :site => 'http://test.api.seek.com.au/v1')
client.auth_code.authorize_url redirect_uri: ''
token = client.auth_code.get_token 'authorization_code_value',
headers: {'Authorization' => %^Basic #{Base64.encode64 "#{username}:#{password}"}^ }
I think the problem relies in the scope method created by OAuth (see bottom of the first snippet), but I'm not sure and anyways I couldn't find a way to amend it.
I also opened an issue in GitHub, but I think this is covered, just it's not documented (or I can't find it).
Ruby (Rails) implementation
This implementation is not using any wrapper, I tried the gem OAuth2 but I was not able to get the grant code,
I presume because the third party implementation require a customization of the scope which I was not able to set with the gem.
module Api::Base
CONFIG = YAML.load_file "#{Rails.root}/config/api.yml"
HOST = 'https://api.com.au/v1/'
REQUEST_URIS = {
get_grant: HOST + 'OAuth/auth',
post_for_token: HOST + 'OAuth/token',
get_applications: HOST + 'advertiser/applications'
}
def uri_for(request, params = {})
uri = REQUEST_URIS[request]
uri += '?' + params.to_param if params.any?
URI.parse uri
end
end
class Api::OAuth2
include Api::Base
# by account_id
##tokens = {}
def initialize(account_id)
#account_id = account_id
end
def self.authenticate!(account_id)
new(account_id).authenticate!
end
# eg: when a request responded that the token is expired
def self.expire_token(account_id)
##tokens.delete account_id
end
# authentication
def authenticate!
##tokens[#account_id] ||= begin
grant = get_grant
raise StandardError.new(#error) if #error
puts "Retrive token for #{#account_id}"
post_for_token
end
end
private
# part of t he authentication process
# as we have one account for many entities, we use third party variation
def get_grant
uri = uri_for :get_grant, {response_type: :code, client_id: username, scope: scope}
http = Net::HTTP.new uri.host, uri.port
http.use_ssl = uri.port == 443
puts "SSL not set for uri #{uri}" unless http.use_ssl?
response = http.get uri.to_s
raise Exception.new(response.message) unless response.is_a? Net::HTTPFound
params = response['location'].split('?').second
#error = params.split('error=').second
#grant_code = params.split('code=').second
end
# part of the authentication process
def post_for_token
uri = uri_for :post_for_token
request = Net::HTTP::Post.new uri.path, {'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'}
request.set_form grant_type: 'authorization_code', code: #grant_code, redirect_uri: ''
request.basic_auth username, password
http = Net::HTTP.new uri.host, uri.port
http.use_ssl = uri.port == 443
response = http.start {|http| http.request request}
JSON(response.body)['access_token']
end
end
def username
CONFIG['credentials']['username']
end
def password
CONFIG['credentials']['password']
end
def scope
"urn:api:thirdparty:username:#{username},urn:api:advertiser:identity:#{#account_id}"
end
end
I'm still planning to use OAuth 2, I'll post my updates here if any

'SSL not supported' when calling https web service in Ruby

I am trying to run the following code to call a webservice from ruby but get an error 'SSL not supported'.
I have httpclient 2.1.5.2 installed.
require 'soap/wsdlDriver'
def validate_certificate(is_ok, ctx)
cert = ctx.current_cert
unless (cert.subject.to_s == cert.issuer.to_s) #check the server certificate only
is_ok &&= File.open(file_name).read == ctx.current_cert.to_pem
end
is_ok
end
def test_webservice
wsdl = "https://.../service.wsdl"
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
driver.options['protocol.http.ssl_config.verify_callback'] = method(:validate_certificate)
driver.method(params)
end
could this be your problem?
http://groups.google.com/group/soap4r/browse_thread/thread/e4d87acac8fe51d5

Resources