Why is sessions reset in my rails application? - ruby-on-rails

I has some features which settings I save in session. But after 1 times reload they reset and session values doesn't exist there.
First load I get session
{"session_id"=>"xxx"}
After save value I get
{"session_id"=>"xxx", "value"=>"100"}
And when I reload my page again I get reset session
{"session_id"=>"xxx"}
Why it can be?

Igor is exactly right. If that happens to be the case in your application, do something like this:
$(document).ajaxSend((event, jqxhr, settings) ->
jqxhr.setRequestHeader("X-CSRF-Token", $('meta[name="csrf-token"]').attr('content'))
jqxhr.setRequestHeader("X-CSRF-Param", $('meta[name="csrf-param"]').attr('content'))
return
)
Now that's CoffeScript, and that assumes you are using JQuery. Regardless, the point is you need to send the CSRF metadata along for the ride with your Ajax requests.

Most likely you're not passing the CSRF token in one of your (probably AJAX) requests. If rails receives invalid CSRF token - it resets the session.
Check out: http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
Update: Vidya is right. You may also want to add the following code to your ApplicationController - to set the XSRF token for AJAX calls
after_filter :set_csrf_cookie
def set_csrf_cookie
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

Thanks for every one, I find problem but I can't explain it.
Later I remove row from js file app/assets/javascripts/application.js
//= require jquery_ujs
When I put it back, it's stop reset session values.

Related

Rails Rpec InvalidCrossOriginRequest

I have a test in rspec for a destroy that returns the following:
ActionController::InvalidCrossOriginRequest:
Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
I can run the destroy just fine thru the UI but when it gets tested I get the above warning. Why is that?
Is your destroy request supposed to be an AJAX request? If so, you can assign the xhr option to true.
get some_path, xhr: true

Detecting Rails 4 Session cookie tampering

Background
I'm an experienced web developer (mostly with Python and CherryPy) who has implemented secure session management from scratch before, and is now learning Rails. I'm investigating the behavior of Rails sessions as exposed by the session object that is available in the ActionController instance and view contexts.
Question/Problem
I have read that the default implementation of sessions in Rails 4 uses an encrypted and tamper-proof cookie. Cool, I guess that means I can use it to hold a user ID for user sessions without worrying about session forging (tamper-proof) or anyone being able to find out what their ID is (encrypted). I wanted to test this and see what rails would do if the session cookie was altered.
So, I went and altered the content of the session cookie attribute using a browser add-on, and when I reload the page with the new cookie value, Rails just happily gives me different new values for session_id and _csrf_token.
What happened to session cookie integrity!?
Shouldn't rails detect (via HMAC signature) that the cookie was altered and then tell me about it somehow?
I'm terrified that I'm missing something obscenely obvious, but I've been having no luck searching for an answer on the web, and the source code isn't giving it up easily either (I'm new to ruby). Thanks in advance.
The Experiment
I created a new app and generated a controller with an index action:
$ rails new my_app
$ cd my_app; rails g controller home index
Then I added these two lines to the app/views/layouts/application.html.erb file:
<%= session.keys %><br/>
<%= session.values %>
I started up the dev server and navigated my browser to "localhost:3000/home/index". As expected, the page has the following lines at the bottom:
["session_id", "_csrf_token"]
["8c1558cabe6c86cfb37d6191f2e03bf8", "S8i8/++8t6v8W8RMeyvnNu3Pjvj+KkMo2UEcm1oVVZg="]
Reloading the page gives me the same values, although the app sets a new value of the _my_app_session cookie attribute every time. That seems weird to me, but I'm getting the same session hash values, so I guess it's cool.
Then, I used a cookie editing add-on for Chrome to alter the value of the _my_app_session cookie attribute (replacing the first character of the attribute value). Reloading the page shows completely different values without anything happening. WAT?
I can't claim a really thorough understanding of the code here. But I can tell you this much:
I followed your steps exactly (using Ruby 2.0.0-p247 & Rails 4.0), with one exception -- I also added the 'byebug' gem to my Gemfile and inserted a debugging breakpoint in the HomeController#index action.
From the byebug console, at that breakpoint, I could see the unedited session cookie via:
(byebug) cookies["_my_app_session"]
"cmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
And I could see the actual encrypted values with
(byebug) cookies.encrypted["_my_app_session"]
{"session_id"=>"13a95fb545a1e3a2d4e9b4c22debc260", "_csrf_token"=>"FXb8pZgmoK0ui0qCW8W75t3sN2KLRpkiFBmLbHSfnhc="}
Now, I edit the cookie by changing the first letter to "A" and refresh the page:
(byebug) cookies["_my_app_session"]
"AmtWeEc3VG5hZ1BzUzRadW5ETTRSaytIQldiaTMyM0NtTU14c2RrcVVueWRQbncxTnJzVDk3OWU3N21PWWNzb1IrZDUxckdMNmZ0cGl3Mk0wUGUxU1ZWN3BmekFVQTFxNk55OTRwZStJSmtJZVkzVmlVaUI2c2c5cDRDWVVMZ0lJcENmWStESjhzRU81MHFhRTN4VlNWRlJKYTU3aFVLUDR5Y1lSVkplS0J1Wko3R2IxdkVYS3IxTHA2eC9kOW56LS1IbXlmelRlSWxiaG02Q3N2L0tUWHN3PT0=--b37c705a525ab2fb14feb5f2edf86d3ae1ab03c5"
(byebug) cookies.encrypted["_my_app_session"]
nil
So the session is nil at this point in the request:
(byebug) session
#<ActionDispatch::Request::Session:0x7ff41ace4bc0 not yet loaded>
I can force loading the session with
(byebug) session.send(:load!)
and when I do, I see that the resulting session id is
"f6be13fd646962de676985ec9bb4a8d3"
and sure enough, when I let the request finish, that's what I see in the view:
["session_id", "_csrf_token"] ["f6be13fd646962de676985ec9bb4a8d3", "qJ/aHzovZYpbrelGpRFec/cNlJyWjonXDoOMlDHbWzg="]
I also have a new cookie value now, unrelated to the one I edited.
So from this I think we can conclude is that what's happening is that since the cookie signature could not be verified, the session was nullified and regenerated. I now have a new session, with a different csrf_token.
The relevant code appears at actionpack/lib/action_dispatch/middleware/cookies.rb:460-464, in the EncryptedCookieJar class:
def decrypt_and_verify(encrypted_message)
#encryptor.decrypt_and_verify(encrypted_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
nil
end
Rather than decrypting a message with an invalid signature, we just treat it as nil. So the unverifiable cookie that stores the session id and csrf token is not used to load the session, and anything that depends on the values in the cookie will fail.
So why didn't we get an error rather than just a new session? That's because we didn't try anything that depends on the encrypted values. In particular, although we have
protect_from_forgery with: :exception
(as opposed to :null_session) in ApplicationController, Rails does not verify the csrf token on GET or HEAD requests -- it relies on the developer to implement these actions according to spec, so that they're non-destructive. If you tried the same thing on a POST request, you'd get an ActionController::InvalidAuthenticityToken error (as you can easily verify for yourself).

automatic logout after inactivity/idle

How to set up in the rails application that if any user is idle for 30 minutes or a specific period of time he should be automatically get logged out.
Can any one give any solution . i am using devise for authentication purpose. Any help is appreciated .
You should use Timeoutable model trait.
Timeoutable takes care of veryfing whether a user session has already expired or not. When a session expires after the configured time, the user will be asked for credentials again, it means, he/she will be redirected to the sign in page.
Options
Timeoutable adds the following options to devise_for:
+timeout_in+: the interval to timeout the user session without activity.
In your model you need
devise :timeoutable
# along with :database_authenticatable, :registerable and other things.
Also, take a look at config/initializers/devise.rb, you can configure timeout value there.
I know this question has already been answered, but I thought I would provide my solution as well, since in my case I was looking for more functionality than even Devise's timeoutable feature could provide. A big thanks to #Sergio Tulentsev for providing me with helpful explanations and ideas in the comments section of his answer!
Problem
Because Devise runs on the server side and not the client side, when the authentication token times out, the client is not aware of the timeout until the user performs an action that calls a Rails controller. This means that the user is not redirected to the login page on timeout until they perform an action that calls a Rails controller.
In my case, this was a problem because my web page contained user-sensitive information that I did not want displayed indefinitely if the user forgot to logout and no action was performed on the page.
Solution
I installed the gem auto-session-timeout, which adds code to the client side to periodically check if the authentication token has expired.
Dependencies
It doesn't say in the ReadMe, but auto-session-timeout requires jquery-periodicalupdater in order to work. This page contains the reason why:
Configuration
Here are the steps I took in order to get auto-session-timeout to work with Devise:
First of all, I followed the steps here in order to customize the Devise sessions controllers. Just for reference, my config/routes.rb file is set up in the following way:
Myapp::Application.routes.draw do
devise_for :users, controllers: { sessions: "users/sessions" }
#other routes
end
In app/controllers/users/sessions_controller.rb, I have the following code:
class Users::SessionsController < Devise::SessionsController
layout 'login' #specifies that the template app/views/layouts/login.html.erb should be used instead of app/views/layouts/application.html.erb for the login page
#configure auto_session_timeout
def active
render_session_status
end
def timeout
flash[:notice] = "Your session has timed out."
redirect_to "/users/sign_in"
end
end
In app/controllers/application_controller.rb, I have the following code:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate_user!
auto_session_timeout 30.minutes
end
Note that we set the authentication token expiration time to be 30 minutes using auto_session_timeout. This replaces the Devise timeoutable functionality.
In my app, I have two layout templates - one for the all the pages that the user sees when they are logged in (app/views/layouts/application.html.erb), and one just for the login screen (app/views/layouts/login.html.erb). In both of these files, I added the line below within the html <body> element:
<%= auto_session_timeout_js %>
This code will generate Javascript that checks the status of the authentication token every 60 seconds (this time interval is configurable). If the token has timed out, the Javascript code will call the timeout method in the app/controllers/users/sessions_controller.rb file.
Note that I have included this code on the app/views/layouts/login.html.erb page. The reason for this is because if there is no activity on the login page for more than 30 minutes (or whatever the auto_session_timeout setting is in the application_controller.rb file), then the authentication token will expire, and the user will receive an Invalid Authentication Token error when trying to login. Adding the code <%= auto_session_timeout_js %> will cause the login to be refreshed when the authentication token expires, thus preventing this error from occurring.
Using Devise Gem:
We can use inbuilt feature of the devise gem but it will not automatically redirect to the sign in page after timeout, redirection will be done after we perform any action.
We can Perform automatically sign out:
By using gem "auto-session-timeout"
https://github.com/pelargir/auto-session-timeout
The disadvantage of using this gem is that it will logout automatically if user is only typing(performing key press event) till the timeout time.
we can override the disadvantage by using Javascript:
Step 1: Define the routes
get 'application/session_time'
Step 2: JavaScript will contain
$(document).ready(function(){
if($("#user_logged").is(":visible") == true )
{
$(document).on( "keypress keydown", function () {
console.log("hello");
$.ajax({
type:"GET",
url:"/application/session_time",
dataType:"html",
});
});
}
});
Step 3: application controller will contain:
#session_time = 5.minute
auto_session_timeout #session_time
def session_time
#session_time = 5.minute
end
Step 4: div to find it is sign in page or not
<% if user_signed_in? %>
<div id="user_logged"></div>
<% end %>
The blank div is kept because we have to load JavaScript only when user is logged in ,so instead of finding current user is nil or not.
I have done with the blank div,it will be available if user is loggedin,so in beginning of JavaScript it is checked that div_id "user_loged" is present or not.
There's a simple way of dealing with this that hasn't been mentioned which requires no extra gems or dependencies.
Say in initializers/devise.rb you've set config.timeout_in = 30.minutes and added :timeoutable to your model. Trigger the following javascript on page loads when a user is logged in:
setAccurateTimeout(() => {
window.location.reload();
}, 30 * 60 * 1000); // minutes (from devise setting) * sec * ms
function setAccurateTimeout(callback, length) {
// adjust any discrepencies every 5s
let speed = 5000,
steps = length / speed,
count = 0,
start = new Date().getTime();
function instance() {
if (count++ == steps) {
callback();
} else {
// console.log(`step ${count} of ${steps}, time passed ${count * speed}ms of ${length}ms`)
let diff = (new Date().getTime() - start) - (count * speed);
// console.log(`accuracy diff ${diff}ms, adjusted interval: ${speed - diff}ms`);
window.setTimeout(instance, (speed - diff));
}
}
window.setTimeout(instance, speed);
}
A regular setTimeout could probably be used, even though over time it introduces inaccuracies due to CPU usage. It would likely just trigger the logout reload slightly later than intended.
The server will terminate the session slightly before this finishes due to being initialized prior to javascript on the client side. When the page reloads the browser will end up on the login screen. This method also makes it easy to trigger a warning modal in advance, for example at the 2 minute mark with a countdown showing the remaining seconds and a button which can be clicked to stay signed in.
Extra tip: on a "stay signed in" button, set the url to one of your pages and add the data-remote='true' attribute. When clicked this will fire off a request to the server without reloading the page the user is on, thus fulfilling the activity requirement and resetting devise's timeout without needing to reload or navigate anywhere. Cancel any programmatic page reload, then restart the main timeout.

Prevent Ruby on Rails from sending the session header

How do I prevent Rails from always sending the session header (Set-Cookie). This is a security problem if the application also sends the Cache-Control: public header.
My application touches (but does not modify) the session hash in some/most actions. These pages display no private content so I want them to be cacheable - but Rails always sends the cookie header, no matter if the sent session hash is different from the previous or not.
What I want to achieve is to only send the hash if it is different from the one received from the client. How can you do that? And probably that fix should also go into official Rails release? What do you think?
Rails only adds the session cookie data to the Set-Cookie header if it has been touched. You might be setting things to the values that they already contain - it's not smart enough to check to see if the data is actually different.
edit My response is a little misleading. When you are using the cookie session store, a new cookie is set if the cookie value (after Marshaling) changes.
See actionpack/lib/action_controller/session/cookie_store.rb
For Rails 3 then use this.
env['rack.session.options'][:skip] = true
or the equivalent
request.session_options[:skip] = true
You can find the documentation for it here http://doc.rubyists.com/rack/Rack/Session/Abstract/ID.html
Here is the crucial line:
config.action_controller.session_store = :nil_session_store
See the whole post.

rails auto_complete plugin. how do i pass authenticity token?

I tried the auto_complete text field in rails 2.3.3 and the server says it denied request because of no authenticity token. I can see that the helper doesn't automatically create a parameter for it.
How I can manually do this? And I do not want to disable forgery prevention for this autocomplete.
Honestly, disabling the forgery protection isn't a bad idea if you scope this to just JS.
def index
respond_to |format| do
format.html
format.js do
# your autocomplete code
end
end
end
Make your autocomplete call /things.js instead of /things.
As far as I understand it, forgery protection is not needed for JS responses, and ensuring your autocomplete uses a GET method should also solve your problem. You're displaying a list, you're not modifying state, so use a GET and use the js response.
The forgery prevention is part of the form helper, not the field helper. If you use the full RoR form helper it should work. If it doesn't, please edit your question to include the form code and I'll try to help.
having had a similar problem I found just adding ":method => :get" to the "text_field_with_auto_complete" tag fixed it (as per Brian) - I didn't need to disable forgery protection

Resources