Session across domains in Rails 4 - ruby-on-rails

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.

Related

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.

Rails 4 external redirection and sessions issue

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?

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.

Rails 4 multitencancy with subdomain login management

Scenario: Multi-tenant rails app that uses subdomains and devise
Problem: I want the user to be able to log into mydomain.com then be forwarded to their own subdomain1.mydomain.com address as a logged-in user. Right now they can only log directly into their own subdomain.
I'm a relative Rails newbie and I can't find a simple solution (although it seems like there must be one). Ideally I would like to have mydomain.com and subdomain1.mydomain.com share one cookie, but my skills aren't there for writing custom middleware. Obviously since it's multitenant I can't share one session across all subdomains. Stuck on this for a few days and curious if there is a simple solution (such as a config.session_store domain setting) that I'm missing before I start looking at OAuth or other more cumbersome solutions. Any help will be appreciated!
Edit: Of course I only found this after posting. Log a user into their subdomain after registration with Rails and Devise . Will try the config.session_store domain: :all with a before filter recommendation and post any details if it doesn't work, seems like a good idea at least.
Edit: SOLUTION that worked for my particular Devise with subdomains setup:
class ApplicationController < ActionController::Base
before_action :check_subdomain
def check_subdomain
unless request.subdomain == "" or request.subdomain == session[:subdomain]
redirect_to request.protocol+request.domain
end
end
end
session_store.rb
My::Application.config.session_store :cookie_store, key: '_my_session' , :domain => :all, :tld_length => 2
Basically I set the subdomain in the session with session[:subdomain] at login and use that to scope the session to the current user. Otherwise when the domain is set to :all in session_store it breaks the scope. If the user is not authorized it redirects them to the public home page via the request.protocol (http:// or https://) +request.domain redirect. Simple! Now users can move between the base domain and their subdomain within the same session.
Cookie
From what you've posted, I'd estimate you have a problem with the tracking of your session cookie. We had a similar problem with our subdomain-powered application, which lead to the cookie being dropped each time you switched between the two
We found the remedy here:
Share session (cookies) between subdomains in Rails?
#config/initializers/session_store.rb
Your_App::Application.config.session_store :cookie_store, key: '_your_app_session', domain: :all, tld_length: 2
The trick is the tld_length argument - this allows you to define how many "levels" of the domain can be accommodated; IE if you're using a sub domain, you'll need to set the tld_length to reflect it
Forwarding
I'm not sure whether you have a problem with your forwarding or not; I'll give you some ideas anyway.
When you log into a "subdomain", unless you've got a true multi-tenancy implementation of Rails (where each user is stored in a different database), you should be able to allow the users to login on the main form, and then redirect them to the subdomain without an issue
Something you need to consider is the subdomain constraint will only be populated if you use _url path helpers:
<%= link_to "Your Name", path_url(subdomain: "subdomain_1") %>
The reason for this is the _path helper is relative to the base URL, and consequently cannot populate the subdomain option. Alternatively, the _url path helper points to the URL in its entirety -- allowing you to define the sub domain as required
--
If you send the request & continue to want the user to remain signed-in, you'll need to ensure you're able to persist the authentication across the sub-domains. IE if you have a single-sign in form on the "main" page, you'll want to ensure you can continue the authentication into the subdomains

Issue with session redirect in rails 3

I'm having a little trouble using sessions in rails 3. I will detail some of my work environment first.
I have an application hosted on heroku, let's say your URL is http://myapp.herokuapp.com
And I have a domain with CNAME pointed to heroku, let's say it's http://www.myapp.com
When I send an email from my app to the client, it contains a url to a restricted area of the application, then the way that is done is:
http://www.myapp.com -> Email -> http://www.myapp.com/secret
But how is a secret area the user is redirected to http://www.myapp.com/log_in
Here's the problem: The Rails saves the actual URL of the application, which in this case is http://myapp.herokuapp.com, and after login it redirects the user to http://myapp.herokuapp.com/secret! And I do not want it, want it to continue in the field http://myapp.com.
Is there any way to do this?
try this :
redirect_to secret_path( host: 'myapp.com' )
or
redirect_to url_for( action: 'my_action', host: SOME_DEFAULT_HOST )
edit
I'm not sure i understood your question well - do you mean you save the URIs in the DB ?
IMHO, saving hardcoded urls can become a hassle.
If possible, try to save deconstructed URI parts instead of a full string path, so you can send the args to url_for or any path helper later (and tweak it as needed, or update your whole table at once to change the host for example).
If not, you can always parse the saved URI with any lib of your choice, and then tweak it before redirection

Resources