Rails 4 external redirection and sessions issue - ruby-on-rails

I am trying to build a website in Rails 4 to track users redirects and site element views.
I decided to use session ids which I believe are quite unique in the short term but I'm having a strange issue.
Example procedure:
user follows a redirect, the system stores this action with a Session ID, let's say xxx
user reaches destination page, which contains a tracker, the system stores this action with ANOTHER Session ID, yyy
user reaches another page which also contains a tracker, the system stores this action with Session ID yyy
After the second action is stored, the session ID stays the same yyy for every request after that, but I need to have the same session ID every time.
In session I also store a SecureRandom.hex generated code, which also changes from the first to the second request (which is not a surprise, since the session ID changes).
I also tried using a cookie, same result.
Please notice that these redirects are external, but all the requests are then made to the same domain (exactly the same, without www and in https).
Any idea?
Thanks in advance.
Update
this is the source code responsible for managing redirects:
before_action :load_redirect, :only => [:http_redirect]
def http_redirect
raise ActionController::RoutingError.new('Redirect has been disabled') unless #redir.enabled
ua = UserAction.create(
:session_id => session.id,
:user_agent => request.user_agent,
:trackable => #redir,
:ip_address => request.remote_ip,
:referer => request.referer
)
redirect_to #redir.destination_url
end
private
def load_redirect
#redir = Redirect.find(params[:id])
end

UPDATE:
Since you are using an iframe (per comment discussion below) for tracking code, the issue is likely that on the external site cookies are not being passed from parent page to the iframe because the iframes origin (domain) is different from the parent page.
OLD ANSWER:
(Still could be helpful for others debugging similar issues)
Source code would help. Without that, here are a few things to try:
Try disabling CSRF protection for the external tracking link action (I'm assuming it POSTs or PUTs data from an external source). CSRF protection could be creating a new or null session for those requests. Put this in the controller that contains the action accepting data from the external source:
protect_from_forgery :except => [:your_action]
The redirect (especially if it's a 301) could be cached in the browser you are using, hence having a different cookie and session than the request your tracking code makes. The stale cookie would be part of the cached redirect.
Try putting cache control headers on your controller action that does the redirect.
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
Your browser may not support setting cookies on a redirect, or possibly third-party cookies. Try in a different modern browser?
There could be a bug in your code. If these solutions don't work, maybe post it?

Related

Where is the Session Stored in Rails?

In Rails, I have implemented the below code for user auth (confirmed to be correct). However, I wanted to confirm my thinking for this strange session[:session_token]. is this the "cookie" that is stored in the browser?
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user, :signed_in?
private
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
def signed_in?
!!current_user
end
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
def sign_out
current_user.try(:reset_token!)
session[:session_token] = nil
end
def require_signed_in!
redirect_to new_session_url unless signed_in?
end
end
My understanding so far of how this works is that whenever the browser/client sends a request to rails, the cookie (with the session[:session_token]) is also sent over, thus allowing the current_user method to find the user. Is my understanding correct? This is strange to me because there's a gap of knowledge of how exactly the browser/client gets access to the session cookie when we declare it in ApplicationController (Rails-side).
You are pretty much there. Although, I have a feeling you might be confusing apples with oranges...
Sessions:
Very often in dynamic web sites one would want to store user data between HTTP requests (because http is stateless and you can't otherwise associate a request to any other request), but you don't want that data to be readable and/or editable on the client-side inside of the URL (like.. yourwebsite.com/yourPage?cookie=12345&id=678), and so on..., because you don't want the client to play around with that data without passing through your server-side code.
One way to solve this problem is to store that data server-side, give it a "session_token"(as you called it), and let the client only know (and pass back at every http request) that token. This is how the session is implemented.
Cookies:
The most common technique for implementing sessions in Rails involve using cookies, which are small pieces of text placed on the user’s browser. Because cookies persist from one page to the next, they can store information (such as a session_token or whatever else you want) that can be used by the application to retrieve the logged-in user from the database.
Where is the Session Stored in Rails?
Using both of the above concepts I can now tell you that the default session store inside of Rails is CookieStore, which is about 4KB in size.
To put it simply...
def sign_in(user)
#current_user = user
session[:session_token] = user.reset_token!
end
...method that you defined places the user into a temporary session.
Then the idea is that the following...
def current_user
#current_user ||= User.find_by_session_token(session[:session_token])
end
...method would find and retrieve the user from the database corresponding to the session token and initialize it to a variable you specified.
Additional info:
You should also note that there is an important difference between Rails's session and cookies helper methods...
They both generate cookies, however, session[...] method generates temporary cookies, which should expire upon the browser exit, and cookies[...] method creates persistent cookies, which do not.
Additionally, I would suggest having a look at Section 2 of Ruby on Rails Security guide. You might find it useful.
Hope this helps you out.
Session is stored in server side. And,
Cookie is stored in client side (in browser cookie). And,
When client/browser send a request to rails server, every time cookies are sent to rails server.
When a session is set in rails server, like: session[:user_id] = 4,
Rails store it in server side.
Session is saved in server side like key value pair (like json object)
For each browser, Rails set a session identifier in cookie, so that, Rails can find the correct session information for a request.
Without session identifier in cookie, Rails do not know, what session belongs to what browser.
So, session will not work without cookie.
Edit: Explain: sessions are stored server side
Suppose, I am using your web application, and after login I will be redirected to home page.
I open login page, input username and password, and click login button.
The form is submitted to sessions#login action.
in sessions#login - you check username and password - and set session[:session_token]:
if username and password is correct
random_unique_identifier_string = #user.remember_token
session[:session_token] = random_unique_identifier_string
redirect_to root_url
end
When server run this code session[:session_token], server need an unique identifier for each browser session.
So, server generate an unique identifier for this browser, such as: abc123
Server set all session variables in a place (may be in some folder or in database), label this folder as abc123.
Now server send a cookie request to browser - to set cookie _ebook_session = abc123.
(I see, if my app name is ebook, in rails, cookie name is like: _ebook_session)
Now the page redirect to home page.
** Note: Everything above happen in single request **
Now, in my browser, I want to open some page that need authentication (suppose, dashboard page).
You added before_action: require_signed_in! in dashboard controller.
So, when I open dashboard page in my browser, browser by default send all cookies with every request. so _ebook_session cookie is sent to server. Your server gets the value of _ebook_session cookie is abc123. Now your application know we need to look in abc123 folder for session. Now you can get value of session[:session_token] from abc123 folder.
** I have explained second request above **
Each browser needs unique session identifier.
Important: _ebook_session cookie will be set in browser in first request. If we already have _ebook_session cookie set in a browser, we do not need to set it again, second, third and next requests in that specific browser.
I hope, you understand.

Session across domains in Rails 4

I have an issue with wanting to use session across domains (not subdomain). Eg, I have .co.uk, .com.au, and .com all for the same address.
I know for subdomains I can use something like:
SomeApp::Application.config.session_store :cookie_store, key: '_some_app_session', domain => :all, :tld_length => 2
But I would like my solution to work between actually domains to have one set of sessions/cookies.
As your default session store is 'cookie_store'
You could just do it the same way as when you might send an email link with an authentication token. Check to verify that the cookie is correct on example.org and, if it is, redirect them to:
http://example.com?token=
and then check to make sure the token matches the one you have in the DB when they arrive. If the token does match, create the session cookie for the example.com domain and then change the token in the database.
This will successfully transfer from one domain to another while providing persistent login on the new domain (via cookie) and shutting the door behind them by changing the authentication token in the DB.
EDIT
To answer your question below, I don't think you need middleware or anything fancy. You could do a simple before filter in the application controller of example.org, something like:
before_filter :redirect_to_dot_com
...
def redirect_to_dot_com
url = "http://example.com" + request.fullpath
url= destination + (url.include?('?') ? '&' : '?') + "token=#{current_user.token}" if signed_in?
redirect_to url, status: 301
end
That will redirect the user either way, and append the token to the query if the user is signed in on the .org site.
Go to more details on Persisting user sessions when switching to a new domain name (Ruby on Rails)
I wouldn't use the PHP style routings which pass ?php=bad style variables via :get especially if you're already using sessions. And also since then you'd have to parse the original URL and a bunch of other work.
Instead of using session[:edition_id] = 'UK' you can use:
cookies[:edition_id] = { value: 'UK', domain: 'some-app.com', expires: 1.year.from_now }
# or if you want to be google 10.years.from_now
When you use session[:edition_id] = 'UK' the value will be encrypted by rails and stored in the _myapp_session cookie. But in your case that probably doesn't matter much.
If you set the cookie explicitly on the domain you want to read it from, it will work without having to set odd ball variables via get and then trying to interpret them again on redirect.

Is it safe to accept URL parameters for populating the `url_for` method?

I am using Ruby on Rails 4.1.1 and I am thinking to accept parameters (through URL query strings) that are passed directly to the url_for method, this way:
# URL in the browser
http://www.myapp.com?redirect_to[controller]=users&redirect_to[action]=show&redirect_to[id]=1
# Controller
...
redirect_to url_for(params[:redirect_to].merge(:only_path => true))
Adopting the above approach users can be redirected after performing an action. However, I think people can enter arbitraryparams that can lead to security issues...
Is it safe to accept URL parameters for populating the url_for method? What are pitfalls? What can happen in the worst case?
By logging params during requests to my application I noted Rails adds always :controller and action parameters. Maybe that confirms url_for can be used the above way since it is protected internally and works as-like Rails is intended to.
This it is safe internally as Ruby On Rails will only be issuing a HTTP redirect response.
As you are using only_path this will protect you from an Open redirect vulnerability. This is where an email is sent by an attacker containing a link in the following format (say your site is example.com).
https://example.com?foo=bar&bar=foo&redirect=http://evil.com
As the user checks the URL and sees it is on the example.com domain they beleive it is safe so click the link. However, if there's an open redirect then the user ends up on evil.com which could ask for their example.com password without the user noticing.
Redirecting to a relative path only on your site fixes any vulnerability.
In your case you are giving users control of your controller, action and parameters. As long as your GET methods are safe (i.e. no side-effects), an attacker could not use this by creating a crafted link that the user opens.
In summary, from the information provided I don't see any risk from phishing URLs to your application.
Rails redirect_to sets the HTTP status code to 302 Found which tells the browser to GET the new path as you defined it by url_for. GET is a considered a safe method in contrast to
... methods such as POST, PUT, DELETE and PATCH [which] are intended for
actions that may cause side effects either on the server, or external
side effects ...
The only problem would have been if someone could gain access to methods such as create and destroy. Since these methods use HTTP methods other than GET (respectively POST and DELETE) it should be no problem.
Another danger here is if you go beyond CRUD methods of REST and have a custom method which responses to GET and changes the database state:
routes.rb
resources something do
member do
get :my_action
end
end
SomethingController
def my_action
# delte some records
end
For future ref:
Rails has a number of security measurements which may also interest you.
It's not exactly an answer, just wanted to point out that you shouldn't use something like
url_for(params)
because one could pass host and port as params and thus the url could lead to another site and it can get worse if it gets cached or something.
Don't know if it threatens anything, but hey, it's worth pointing out

rails not picking session data

I am implementing a payment gateway in my app.
Its like this:
The user fills the form with necessary details, along with a field containing return_url(say http://myapp.com/ebs_payment/ebs_response?dr={somedata}) and submit the form to a secure payment site. After the transaction is complete, the secure site puts some encrypted data into my param {dr} and the user is redirected back to the return url. The problem here is, when the user returns to the app with the return_url, the application fails to pick up the session data and returns a nil value.
Before submitting the form, I put the object #fdPaymentDets in to session.
Here is my controller:
class EbsPaymentController < ApplicationController
#before_filter :login_required,:check_user_authenticate
#access_control [:ebs_response] => ('user')
def ebs_response
#fdPaymentDets = session["fd_payment_details"]
#deal = Deal.find(#fdPaymentDets.deal_id)
#categories = find_all_categories
end
private
def find_all_categories
#sp = SitePreference.find(:first)
Category.find(:all,:limit => #sp.categories_display_limit,:order => 'position')
end
end
When the user is redirected to the return url (http://myapp.com/ebs_payment/ebs_response?dr={encrypted_data}) from the secure site, rails is not picking the #fdPaymentDets object from session and making it nil thus resulting in an error when accessing data from the nil object.
The surprising thing is that, when I put the same return_url in my browser by hand, the session data is neatly picked and everything goes well.
Am missing any thing here? What could be the obvious reason?
Any help appreciated.
Cookies and redirects are messy and bug prone (from a browser's implementation perspective).
Take a look at
Safari doesn't set Cookie but IE / FF does
Suggestion would be to change the implementation to set the session first in the show action, and then update the value before the redirect

redirect_to doesn't work well for RESTful apps?

As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.

Resources