58

I need to pass some parameters to callback action. Judging from the source code, OmniAuth should add query string to callback URL but strangely it does not. When I open

/auth/facebook?from=partner

...and get redirected to Facebook, return_url is just

/auth/facebook/callback

...without any parameters.

jefflunt
  • 33,527
  • 7
  • 88
  • 126
synapse
  • 5,588
  • 6
  • 35
  • 65
  • 2
    http://stackoverflow.com/questions/7999907/passing-random-url-params-to-omniauth – Zabba Apr 21 '12 at 07:55
  • 41
    Your "from" parameter can be retrieved through env["omniauth.params"] – David Morales Aug 10 '12 at 23:35
  • 1
    See also if using Devise: [Devise + Omniauth - How to pass extra parameters along?](https://stackoverflow.com/questions/6657727/devise-omniauth-how-to-pass-extra-parameters-along) – Damien Oct 08 '15 at 05:52

7 Answers7

82

After struggling with all the above answers, I figured out what to do regarding Facebook, which by default does not display the params in request.env["omniauth.auth"].

So -- If you are using a query string for the callback, similar to something like this:

"/auth/facebook?website_id=#{@website.id}"

The only way to get that website_id param is by using request.env["omniauth.params"]. NOTE: MAKE SURE YOU USE omniauth.params and not omniauth.auth -- this one tripped me up for a while.

Then, to test this out, you can inspect it within your controller action (notice the RAISE line...):

def create
  raise request.env["omniauth.params"].to_yaml 
  # the rest of your create action code...
end

You should see your parameter there. Great. Now, go back to your controller and remove that RAISE line. Then, you can access the param as follows in your controller action:

params = request.env["omniauth.params"]
website_id = params["website_id"]

NOTE: in params["website_id"] you need to use quotes and NOT a symbol.

nfriend21
  • 2,170
  • 1
  • 21
  • 21
  • I got output in omniauth.auth but nil in omniauth.params. What is wrong? i already pass parameter in URL. "/auth/facebook/state=123" – harsh4u Sep 26 '14 at 09:57
  • 1
    @harsh4u probably because your URL is lacking a '?' before the parameters. It should be "auth/facebook/?state=123" – Oleksandr Kruk Nov 28 '14 at 00:12
  • 1
    Thank you for this!! I was hijacking the state parameter to achieve this before reading your answer. Thanks! – septerr Mar 14 '15 at 19:22
  • 2
    This is a great feature of omniauth, shame it's not better documented. – SirRawlins Mar 28 '17 at 09:48
22

I guess the cookie thing works but why do all that when you can use the state variable as documented here: https://github.com/mkdynamic/omniauth-facebook

This is how I used it:

when creating the url you can just add state in the Query String and it will be available in the callback url as well.

user_omniauth_authorize_path(:facebook, :display => 'page', :state=>'123') %>

now the callback url will be

http://localhost:3000/users/auth/facebook/callback?state=123&code=ReallyLongCode#_=_

Now in the callback handler you can process the state

user566245
  • 4,011
  • 1
  • 30
  • 36
18

You can use the :params options, as in

omniauth_authorize_path(:user, :facebook, var: 'value', var2: 'value2' )

and later in the callback you can access request.env['omniauth.params'] to get the hash! :)

(copied from this answer)

Community
  • 1
  • 1
Nimo
  • 7,984
  • 5
  • 39
  • 41
8

What you want to do is dynamically set your callback to include the partner name in the url (not the url parameters), on a per authentication transaction basis, depending on which partner was involved. This means setting the callback url dynamically, for each authentication request. See this blog post to get started. The callback url automatically drops the url parameters, as you've noticed, so doing this with parameters won't work.

So, if instead of trying to pass the partner name/id in as a parameter (which is dropped), you structured your routes so that the partner_id and OmniAuth provider were part of the callback url, then you'd have something like:

/auth/:omniauth_provider/callback/:partner_id

...where a valid callback would be something like

/auth/facebook/callback/123456

...then you would know that a given callback came in from facebook, with partner id 123456

jefflunt
  • 33,527
  • 7
  • 88
  • 126
  • Nice answer, Normalocity. I was trying to figure this out myself. I'm not quite sure how to do what you prescribed above and the blog post doesn't shed that much light either. Would you be able to post some code? Specifically, how would generate this route described above and how would one create the respective controller actions to handle the different callbacks? I specifically want the callback to provide context of where the user left from, that way I can return the user to where they were once OAuth is complete. – John Apr 04 '12 at 04:29
  • The url where the user "left from" can be stored in a cookie as they're being redirected to the oauth portal, when when the callback returns, the cookie can be looked up, the value retrieved, and the cookie expired/deleted. As for the other stuff, I think it might be a good idea for me to roll a quick demo app and post it as a proof of concept, and then I can share the code on github. Then, assuming it works, I can post the code back here in the answer. Give me a few hours to take a crack at it. – jefflunt Apr 04 '12 at 13:44
6

OmniAuth already has a built-in way to know where the user was, it's called "origin" as documented here:

https://github.com/intridea/omniauth/wiki/Saving-User-Location

David Morales
  • 17,816
  • 12
  • 77
  • 105
1

You know, I think I might be trying to solve this the hard way.

Cookies might be the answer. I think you can solve this by having your login action store a cookie, and then redirecting to the proper /auth/:provider path for authentication, and when the callback is triggered (in SessionsController#create), you just read the cookie back to know where to redirect them to.

So, right now, your "login with facebook" link (or whatever you have you in your app) probably goes to /auth/facebook. Instead if you created a custom action like

POST /partner_auth

...and called it with the url...

POST example.com/partner_auth?from=partner&provider=facebook

Then you might have a controller like:

class PartnerAuth < ApplicationController
  def create
    cookies[:from] = params[:from]  # creates a cookie storing the "from" value
    redirect_to "auth/#{params[:provider]"
  end
end

Then in the SessionsController#create action, you would have...

def create
  ...

  destination = cookies[:from]
  cookies[:from].delete

  redirect_to destination    # or whatever the appropriate thing is for your
                             # app to do with the "from" information
end

I tried to build a demo app to accomplish what I'd outlined in the other answer, but you're right - it was too complicated to try to dynamically inject a custom callback into the OmniAuth code. There is a configuration option to override the default callback, but it doesn't appear to be easy to set it dynamically.

So, it dawned on me that cookies would be way simpler, user-specific, and since you theoretically only need to store this from information for a very short time (between when the user tries to authenticate, and when the callback is triggered), it's no big deal to create a cookie, and then delete it when the callback gets hit.

jefflunt
  • 33,527
  • 7
  • 88
  • 126
-2

Use the 'state' Variable. Facebook allows the user to set a STATE variable.

Here is how I did it, I appended the AUTH URL with ?state=providername

http://localhost/users/auth/facebook?state=providername

This param is returned to me at Callback as params['providername']

I devised the solution from the original Omniauth Path Method

user_omniauth_authorize_path(:facebook, :display => 'page', :state=>'123') %>
Manmeet Singh
  • 430
  • 5
  • 3
  • This is very bad approach. The state parameter is there to avoid token forgery from another domain. The state is a generated key that is passed with the user to the IDP and is returned by the IDP so you can double-check that this auth request was really made by your server. – Jakub Apr 12 '20 at 15:19