request.env["omniauth.params"] empty during callback phase when using omniauth-stripe-connect - ruby-on-rails

I am attempting to use the omniauth-stripe-connect strategy to connect user accounts so i can transfer funds directly to their accounts during a purchase.
I have the following in an initializer
Rails.application.config.middleware.use OmniAuth::Builder do
provider :stripe_connect, APP_CONFIG[:stripe_connect_client_id],
APP_CONFIG[:stripe_secret_key],
scope: 'read_write',
stripe_landing: 'register'
on_failure { |env| AuthController.action(:failure).call(env) }
end
and the following in an auth controller
def stripe_connect
result = request.env["omniauth.auth"]
pass_through_params = request.env["omniauth.params"]
...
# do some stuff
end
and I initiate the authentication process with
http://test.lvh.me:3000/auth/stripe_connect?user_id=980190962&user_subdomain=test
During the callback request.env["omniauth.auth"] has correct values, but request.env["omniauth.params"] is always {}. request.env["omniauth.origin"] is also nil.
I have gotten this to work in other situations (facebook oauth integration). At a loss as to why those values are not returned as expected.

The issue has entirely to do with how oauth sets the param values. This is managed through values stored on session. I had thought the values were being forwarded to the authenticating service (in this case Stripe) and returned by that service.
Instead, they get placed in session prior to the call, and appended to the oauth values during the callback phase.
My issue was related to my use of subdomains. I was initiating the request on one subdomain and returning (callback url) to a different subdomain. Since sessions aren't maintain across subdomains (at least with our configuration), the params information was not available.
Reworking the process to use the same subdomain during the request phase and the callbacks phase solved the problem.

Related

How to set custom header for all requests that go through OmniAuth?

This is mostly a thought exercise that I couldn't figure out how to solve. :)
Does anyone know how to set a custom request header for all requests that use the omniauth or omniauth-oauth2 gems?
I've tried something like the following, but I don't see the header in my developer tools in Chrome, for example.
OmniAuth.config.before_request_phase do |env|
env['MY_CUSTOM_HEADER'] = 'true'
end
What am I doing wrong here? What am I missing here?
Edit based on responses:
Why I need the custom header set doesn't really matter here -- as I mentioned at the top of the question: this is just a curiosity I had. The constraint is that I'd like to test the custom header on any OS or browser, so it's not sufficient to just play with developer tools in Chrome here as not all browsers have that capability on across all OSes.
What I'm trying to do is add a custom header to all outgoing OAuth requests coming out the gem. That's it.
In browser developer tools you can only see request headers that are sent by or response headers received by the browser. Any request headers set by load balancers, reverse proxies, middleware and so on - are only visible to next stage in request handling chain.
OmniAuth lives in middleware, so technically any request in your app uses it, unless some other middleware terminates request chain and renders some response.
Also "request" in omniauth terms is not http request, it's an auth phase, usually happens on /auth/:provider (also there're "options" and "callback" phases).
It's not clear why you need setting a request header in a middleware on a request that goes into your own application - when request is going to be handled by omniauth it will not hit your controllers, except for callback phase where request.env['omniauth.auth'] is going to be set.
Since you're mentioned developer tools - probably you want response headers on request phase, to set these you need to override rack response returned from request_phase in your strategy. But for oauth2 there's only a redirect (still possible to set headers, but even less sense).
So first you need to be exact on which headers you want to be set at which request/response, there're several of those. Simplified sequence:
An OAuth request handler cannot force a browser (or any similar user-agent) to disclose more information than specified in the HTTP protocol. Be glad of that: any other posture could lead to information leakage.
The only connection that Omniauth might make itself is that exchanging a code for an access/refresh token. That is specific to the strategy in question, but strategies have the opportunity to include arbitrary headers in their internal client. If you're writing a custom strategy that required a basic authentication header during the access token exchange, it might look like this:
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class DemoStrategy < OmniAuth::Strategies::OAuth2
option :name, "demo"
option :client_options, {
site: 'https://api.example.org',
authorize_url: 'https://auth.example.org/oauth2/authorize',
token_url: 'https://auth.example.org/oauth2/token'
}
uid { raw_info['id'].to_s }
info do
{ email: raw_info['email'], image: raw_info['avatar_url'] }
end
extra do
{ raw_info: raw_info }
end
def raw_info
#raw_info ||= access_token.get('user').parsed
end
def build_access_token
options.token_params.merge!(headers: {
'Authorization' => special_auth_header
})
super
end
def basic_auth_header
"Basic " + Base64.strict_encode64("#{options[:demo_id]}:#{options[:demo_secret]}")
end
end
end
end
Here, build_access_token is overriding the superclass's standard constructor for the internal HTTP client, and injecting extra headers before handing it back up the stack. Internally that's handed off to the oauth2 gem, which in turn uses Faraday, so it's likely anything Faraday accepts is a valid option.
If you need additional information carried to the authentication server, it may be encoded in the redirect URL by the strategy. For example, the omniauth-google-oauth2 strategy is configurable to carry authentication scopes and email hints in the URL that lands on Google's authentication endpoint.
It is also common to include a XSRF state parameter, in conjunction with an encrypted session cookie, to protect against identity spoofing. Depending on the co-operation of the authentication server, some or all of this data may be reflected in the redirection back to your handler.
At it simplest, that is handled by the authorize_params method in the strategy subclass e.g.
def authorize_params
super.tap do |params|
params[:something] = 'my_extra_value'
end
end
However, the volume of code involved in setting up extended parameters may be quite substantial in practice. For a worked example of doing this with Omniauth I'd suggest taking a looking at the source code of the Google strategy, and again I'll draw your attention to the authorize_params method which is the entry point for this heavy lifting.
In the overall flow of things, those are the touch points where server-side code can actually influence matters. There's a fundamental expectation that the user's client/browser is participating by executing nothing but normal HTTPS request.

Authenticating docusign via Rails API (Omniauth + Devise) + JS Frontend

I'm trying to create an authentication flow using Auth Code Grant where I've added necessary omniauth strategy for Docusign to create /auth/docusign routes in Rails API only application.
Here are the steps followed
I'm issuing a request to the route from VueJS client.
window.open("http://localhost:4000/auth/docusign", "targetWindow", "width=350,height=250")
After user enters credentials and on successful login I'm calling the callback:
class SessionsController < Devise::SessionsController
def docusign
internal_destroy
#success = false
userinfo = request.env['omniauth.auth']
request_info = request.env['omniauth.params']
if userinfo
info = userinfo.info
cred = userinfo.credentials
user = User.find_by(email: info['email']) || User.find_by(id: session[:user_id])
if user
organization = user.organization
organization.organization_providers.where(provider_name: 'Docusign').destroy_all
OrganizationProvider.create(email: info['email'], token_expires_at: Time.at(cred['expires_at']), token_expires_at: Time.now, provider_name: 'Docusign', organization_id: organization.id, token: cred.token)
#success = true
end
end
render 'sessions/docusign'
end
end
I'd like to pass some params (which I'm accessing in the callback as request.env['omniauth.params']) for executing some backend tasks in the method.
When I try window.open("http://localhost:4000/auth/docusign?email='"+email+"'", "targetWindow", "width=350,height=250")
It says that the url doesn't match with any redirect urls
I have also tried passing in redirect_to('/auth/docusign', query: query) but on doing so, it doesn't open in a browser due to CORS.
I'm also trying to set it in session cookie, but since it's an API only server, I'm still working towards setting up cookie store.
Question
Which is the best way to achieve this? To pass some params in the callback and retrieve it.
Then the execution flow continues on the Rails server and the window serves a page with an appropriate response as per authentication status. However during this time, the client window which started the request is not aware of the authentication outcome.
Question
How can I communicate to the VueJS client that the authentication process is completed?
Question
Am I doing the above flow correctly or are there any better ways to achieve the same?
Thanks in advance
You need to log into your DocuSign Developer Account, Click on Admin and go on the left nav down to "API and Keys" where you can find the integration key you set. Did you set one?
If you did, you should find it and then add the redirectUri to the OAuth settings for that key (client ID in OAuth).
That is why DocuSign login tells you that the redirectURI doesn't match. You can add http://localhost:4000/auth to the list and that should work for your local env.
You cannot past custom variables on the redirectUri, it has to match exactly to the one you entered. If you need to pass values to it, there's a way to do that using state.
Here is how the URL should look, notice the &state= part of it:
https://account-d.docusign.com/oauth/auth?
response_type=code
&scope=YOUR_REQUESTED_SCOPES
&client_id=YOUR_INTEGRATION_KEY
&state=YOUR_CUSTOM_STATE
&redirect_uri=YOUR_REDIRECT_URI
&login_hint=YOUR_LOGIN_HINT
You can put whatever you want in there (URI encoded of course) and that value would come back to you when redirected back also with &state= parameter.
This solves the problem and allows you to pass arguments back to your redirect URI.

GraphQL/ Rails 422 Unprocessable Entity - Saving token to Session

I’m currently working on changing the rails backend of a project from REST to graphql and I’m running into an error with authentication following their tutorial - https://www.howtographql.com/graphql-ruby/4-authentication/
I’m using the GraphiQL engine to test all my requests and receiving error status 422 unprocessable Entity, User Must Exist
Which makes sense because the mutation I am executing is to create a new color - which has a belongs_to relationship to User.
About halfway through the page linked above ^^^ it says this:
With the token that the signinUser mutation provides, apps can
authenticate subsequent requests. There are a couple of ways this can
be done. In this tutorial, we are just going to use the built-in
session, since this doesn’t add any requirements to the client application. The GraphQL server should be able to get the token from
the session header on each request, detect what user it relates to,
and pass this information down to the resolvers.
I’m able to successfully return an auth token through the signinUser method like the docs show previously on the same page - the method that it posts to also saves the token to this supposed session in this method here (also from the same link posted above ^^^) :
def call(_obj, args, ctx)
input = args[:email]
return unless input
user = User.find_by email: input[:email]
return unless user
return unless user.authenticate(input[:password_digest])
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_BASE_KEY"])
token = crypt.encrypt_and_sign("user-id:#{ user.id }")
puts "please **********************************"
p ctx[:session]
ctx[:session][:token] = token
puts "please **********************************"
p ctx[:session]
OpenStruct.new({
user: user,
token: token
})
end
You’ll be able to see in my desperate struggle that I p’d out the session right before the method returns and not surprisingly saw that it contained the token for that users sign in.
However, when I proceeded to execute the mutation to create a color, my expectation was that the session would still contain that token and I’d be able to commit that color successfully. That was not the case and when I p’d out the session for this request, it return an empty hash.
I cant find any information about how the built in graphql session works - and I’m brand new to graphql in general.
My main questions would be - is the graphql session supposed to be caching token information? Why is it that the information is not carrying over to requests following signinUser ? Should I even bother with trying to use the auth in this tutorial since the docs claim that this authentication method is not a long term solution?
I know this is a lot but would really appreciate an extra brain on this.
Thanks in advance!
PS. I understand the tutorial uses links and I am using colors here - that is intentional and I have done my best to make sure that semantic differences were not causing any errors.
Rails version - 5.2.2 (using api only)
graphql - 1.7.4
graphiql rails - 1.4.4
Same as REST APIs GraphQL does not store any information between two subsequent requests, You have to pass authentication token returned in sign in mutation to all subsequent requests where you want current user-related information.
You should do something like below in graphql_controller.rb
class GraphqlController < ApplicationController
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
current_user: current_user
}
result = GraphqlTutorialSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
end
private
# set current user here
def current_user
# you can token here
token = request.headers['Authorization']
return nil unless token
# find current user from this token
end
# Handle form data, JSON body, or a blank value
def ensure_hash(ambiguous_param)
# ...code
end
end
A colleague of mine pointed out that "session" is part of rails and should create a cookie that would be accessible from the next request.
I mentioned that I was using Rails version - 5.2.2 (using api only) - well when you use the -api flag when initializing a new rails project, it adds these lines to application.rb
# 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.
# 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
Notice this line in particular - Middleware like session, flash, cookies can be added back manually.
I commented out config.api_only = true and this added cookies back to the application/ allowed me to make the next request with an existing user.
You can also add these lines I found from "Lysender" on his post - Rails 5 – API Only – Enable Cookies and Sessions if you'd prefer not to remove the api-only feature.
config.api_only = true
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore, key: '_coookie_name', expire_after: 30.days

Rails + Devise -> clearing session cookie

I got a situation like this:
using Rails 4 + Devise
I log out user, during that I got my chrome developer tools opened and check in Network what headers are flying around. The thing that interests me is "sign_out" (action/cookie? i don know the exact name I should use here).
In it's request header i got a session variable "_app_name_session".
If I copy this variable and make a GET request to some part of my site that requires an user to be authenticated then I get 200 OK response (although I have already logged out!)
How could I prevent this from happening?
I have already tried a Warden callback in my User model like this:
Warden::Manager.before_logout do |user, auth, opts|
auth.cookies.delete :_app_name_session
end
and
Warden::Manager.before_logout do |user, auth, opts|
auth.cookies["_app_name_session"] = ""
end
but this doesn't work and the session variable is set anyway in the cookies.
Is it possible to timeout session immediately upon logout?
This really bothers me as although the session should be cleared upon logout but it is not and there is a way (I know, complicated and attacker would have to get through other security layers) to hijack user session.

Multiple values for OmniAuth.config.full_host

I am trying to configure Omniauth and Devise using Stripe and LinkedIn strategies on my SaaS platform.
As part of the platform each client can have their own unique domain.
I can get LinkedIn working on my client sites, but Stripe requires you list all the callback URL possibilities in their admin interface. To work around this I have set www.myapp.com as the callback url via OmniAuth.config.full_host however this means I have a static callback url and then pass throught he client ID via the state parameter.
This works for Stripe, but then LinkedIn begins to fail as the callback domain no longer matches the originating request.
Is there a way to set OmniAuth.config.full_host for only certain strategies (i.e. only Stripe)?
config/initializers/omniauth.rb
OmniAuth.config.full_host = lambda do |env|
if env['omniauth.strategy'].is_a?(OmniAuth::Strategies::Stripe)
#return url for stripe
else
#return url for others
end
end
Another option might be to override OmniAuth::Stragies::Stripe#callback_url.
#config/initializers/stripe.rb
module OmniAuth
module Strategies
class Stripe
def full_host
#return url for stripe
end
end
end
end
The original OmniAuth::Strategy#full_host method will be used for the other strategies.

Resources