Rails + Devise -> clearing session cookie - ruby-on-rails

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.

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.

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.

Rails - expire_after on session_store does not allow session to be changed

My site is a single page rails app that interacts with a rails api. I recently added an api endpoint that allows a user to log out via ajax.
class Api::SessionsController < Api::ApplicationController
...
# DELETE /api/sessions/destroy
def destroy
session.delete(:user_id)
render json: { success: true }
end
...
end
This endpoint was working fine until I altered my session store to keep the session around when a user quits their browser.
config/initializers/session_store.rb diff:
AppName::Application.config.session_store :cookie_store,
- key: session_key, domain: TOP_LEVEL_DOMAIN
+ key: session_key, domain: TOP_LEVEL_DOMAIN, expire_after: 1.month
Now, after hitting my sessions#destroy endpoint via ajax, reloading the page logs the user back in, and still has the user_id in the session.
My guess is that the browser's cookie is not being overwritten by the DELETE ajax request (like it was before I added the expire_after parameter), but this is very tough to verify because the stored cookie is encrypted.
Does anyone have any solution ideas or debugging paths?
Does anyone know what is happening here?
When you set :expire_after to set the expiration of the cookie, it causes the cookie to persist to the given date even when the browser is closed. This means that when you change something in the session of Rails, you have to update the cookie. The simplest to way to achieve that is recreating the cookie.

How does web session work in this rails project?

Introduction:
I am building a facebook app which auto wishes happy birthday. I am building it in Rails and using a ruby API wrapper called fb_graph. The creator of fb_graph has graciously provided a working sample application fb_graph_sample
After playing around with it, I do not understand how the sessions/cookies work. For example, check out this code:
def require_authentication
authenticate Facebook.find_by_id(session[:current_user])
rescue Unauthorized => e
redirect_to root_url and return false
end
def authenticate(user)
raise Unauthorized unless user
session[:current_user] = user.id
end
Where does session[:current_user] comes from?
Under config/initializers/session_store.rb,
FbGraphSample::Application.config.session_store :cookie_store, :key => '_fb_graph_sample_session'
So, I look at the cookies for localhost which is where I am deploying it as using Chrome inspector tools, I see _fb_graph_sample_session with value, domain, path, expires, size, http, etc...
I still don't see how session[:current_user] comes about? Looking at the development.sqlite3 file, there is only 1 data for the facebook model. The id is 1 so, that leads me to believe that [:current_user] is 1 and the code is calling 'authenticate Facebook.find_by_id(1)'
Can someone please explain how session[:current_user] translate to 1? I read railstutorial.org chapter on signing-in-out and it creates a sessions controller but there is no sessions controller in the fb_graph_sample app.
Thanks,
I get's set in the authenticate method:
session[:current_user] = user.id
The app is using cookie based session store, when a user logs in a cookie (think of it as a special hash) is written to his browser. You use session pretty much as a hash, you can set as shown above, or get, i.e.
<%= "logged in as #{User.find(session[:current_user]).name}" %>

Resources