I'm new to web app development & have just started learning Sinatra framework. I'm currently developing a login system.
Here's the necessary code :
require 'sinatra'
require 'openssl'
require 'encrypted_cookie'
configure do
use Rack::Session::EncryptedCookie, :secret => "foo-bar-baz", :httponly => true
Then, I'm storing username in the session if he/she is authorized :
post '/' do
if authorize(params[:name], params[:password], params[:csrfkey])
session[:name] = params[:name]
end
end
The cookie is encrypted all right. And it is also remembering session values.
But while using Firebug I noticed that at every request(page load, refresh, etc) the cookie's content is changed & I mean completely changed. It seems like Sinatra is sending new cookie at every request.
Previously, I was using enable :sessions & the cookie never changed.
So my questions are, is it normal to have cookie's content changed at every request while in a session?
Is it happening because it is encrypted?
I searched high & low on the net but no one has got this problem I guess..
The source for encrypted_cookie shows that it generates different encrypted output every time it is called regardless of the input. There are 2 reasons for this:
The library would have to know what the session value was during the last request. It doesn't, all it does is accept a single input, the given session. If you wished to circumvent this and just rewrite the cookie (I suppose) you could, since you have the extra information available higher up in the Sinatra app.
It's more secure. It doesn't leak information (if the cookie doesn't change then an observer of the cookie knows nothing changed during the request), and it gives an attacker less time to try and get to a meaningful value.
Related
I need to deserialize the session cookie by hand to extract the user from it. The enviroment is rails, though it is not in the http server context, so there is no request for devise to automatically deserialise. There is the Warden::SessionSerializer, though I did not really get how to use it.
Before I wasn't using Devise so I used this method to deserialize the rails session cookie:
def decrypt_cookie(cookie)
cookie = CGI.unescape(cookie)
config = Rails.application.config
encrypted_cookie_salt = config.action_dispatch.encrypted_cookie_salt
encrypted_signed_cookie_salt = config.action_dispatch.encrypted_signed_cookie_salt
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
secret = key_generator.generate_key(encrypted_cookie_salt)
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
encryptor.decrypt_and_verify(cookie)
end
Since some people might be skeptical whether there is a context in which manually deserializing the cookie can ever be proper:
The client (browser) authenticates via HTTP, and the session is of course stored in the cookie. Rails is also running (rails runner) the websocket server that responds to certain privileged actions. For these I need to (re-)authenticate the user. If someone sees a different (better) way of doing that, I'd be glad to read it. :)
The way I describe for deserializing the cookie is just the right way. It is actually the same in Devise, the keys just changed and I jumped to quickly to the conclusion that there must be a different serilization process going on.
Do you have to use Devise? I posted previously on using Warden to roll your own token auth which you have complete control over. If you decide like me to just do the easy thing and implement auth yourself you will see its easy to lookup a user from a token via your models.
You can access the session object in your controller. What key do you use to store the user_id? You should just be able to do session[:key], so if you store the id as user_id, then session[:user_id].
Does the session become nil? Does the change take effect only on the next request?
I think I just asked three questions now...
You can try to explore by using the similar settings:
AppName::Application.config.session_store :cookie_store, key: '_session_key', expire_after: 20.seconds
Then open up dev tools in your browser and go to cookies and select localhost cookies to see what happens.
I found out that:
Session cookie gets deleted after the expiration time
Expiration time for a cookie gets updated automatically (re-set) upon any request (even background ajax request counts)
The effect by default will take place upon the next request (refreshing the page for example) and if you use typical authentication (has_secure_password_ for example) user should be logged out
I found the last comment on the ActionController::Base documentation page really helpful on this topic
Alright, I'm a huge RubyCAS noob, and this is driving me crazy.
I have installed the rubycas-client gem, and have followed along with the official Sinatra setup at this repo. The way this setup is done, every time I request a page, I receive a one-time ticket that's appended to the URL as a query like so:
http://localhost:9393/?ticket=ST-1373928850... etc.
If I refresh the page, I get a Sinatra error saying the ticket has already been used up!
I've two questions, then.
Is the ticket-per-reload standard behavior?
How do I save my CAS login for a session and still retain single sign-out?
What I've done:
I have gone and tried to implement :sessions in Sinatra, but this causes single-sign-out to fail.
I have gone and done my best to follow the steps in the rubycas-client GitHub Repo (replacing ActiveRecord session storage with Sinatra's :session helper).
The RubyCAS documentation for Sinatra is fairly poor, so I'm looking for a definitive answer to this.
It is doing the correct thing when you try and reload the page with the same ticket. That ticket has already been validated. When you get the validation response you need to then set your own applications cookie or other session option.
I usually add a method that will add a session attribute to the user's cookie like:
session["cas"]["username"] = <user from cas validation response>
Then in future requests the Sinatra application can protect whatever routes you want with a helper method like:
cas = RestClient::Resource.new "#{cas_url}/login", :timeout => 5
checked = cas.get
return true if checked.code == 200
In my configure block for Sinatra I do this:
use Rack::Session::Cookie, :key => "example.com",:secret => "veryrandomhex"
I hope this helps, have any questions let me know.
UPDATE BELOW
While discussing this problem, we've uncovered that RubyCas says to not use a regular cookie session for your ruby application in production, while using CAS. What you'll want to do is:
A. Make sure your cookie expires at the same time or sooner than the CAS cookie
And/Or
B. Make sure your cookie is per browser session, then revalidate the CAS user on next browser session.
For Rack cookie you would specify this extra config for when the cookie is set to expire: :expire_after => 1440, (where 1440 is in minutes)
In case of the ruby CAS there are two kinds of session :
(1). The application session.
(2). The Single sign on (SSO) session.
you can use sinatra-session gem for managing the application session and just use session_end! helper method to destroy the application session. For destroying the SSO session unset the session[:cas_ticket] parameter in log out route.
example:
In case of the Sinatra:
get '/logout' do
session_end! # provided by sinatra-session gem
session[:cas_ticket] = nil # session variable set by CAS server
end
here we are explicitly setting the session[:cas_ticket] to nil, however you can use session.clear in logout route to destroy the current session data .
I have a website that is served via HTTP with a custom domain (e.g., mywebsite.com) on Heroku.
When a user wants to login, I redirect them to my Heroku application's non-custom URL via HTTPS (using Piggyback SSL, because I am cheap and don't want to pay for SSL Endpoint).
Then, after they've logged in, I redirect the user back to my custom domain since their traffic doesn't really need to be encrypted anymore.
Only problem is that before redirecting them, I display a message (e.g., "You've successfully logged in!") using flash[:notice] (with sinatra-flash). But I believe that since the user crosses from one domain to another in this process, though still on the same server, they don't see the message since their session data from domain X is not accessible from domain Y.
Is my understanding of the problem correct?
What is the right way to fix it?
Update on 2012-10-12:
So, the solution I ended up going with is a little hacky:
First, I added a new data model, OneTimeFlash, with a token field containing a random string for each record. Then I made it so that after the user logs in on domain X, I create a new OneTimeFlash instance and redirect to domain Y with the token in the query string:
# The model automatically generates a token before being created.
one_time_flash = OneTimeFlash.create(:message => "You've successfully logged in!")
redirect "#{DOMAIN_Y}/flash/#{one_time_flash.token}"
Then when the server detects this token in the request, it displays the flash:
get "/flash/:token" do |token|
one_time_flash = OneTimeFlash.first(:token => token)
flash[:notice] = one_time_flash.message unless one_time_flash.nil?
one_time_flash.destroy
redirect "/
end
(The ORM is DataMapper, in case anyone's confused.)
Like I said: it's hacky, but it works. Can anyone think of any serious problems with this approach?
you can't use flash between domains in that situation since flash is only saved for the next request.
you could use HTML5 localstorage - some random localstorage explanation or w3schools website on html5 localstorage
Everything I read about cookies says that setting the expiry time of a cookie to zero should make it a `session' cookie, which the browser will then delete upon exit.
http://www.cookiecentral.com/faq/ says that :
"...generally a session is the length of time that the browser is open for..."
http://php.net/manual/en/function.setcookie.php says :
"If set to 0, or omitted, the cookie will expire at the end of the session (when the browser closes)."
However, some experimenting in Firefox (3.0.8) shows that:
cookies set as session and secure get deleted on exit
cookies set as session only do not get deleted on exit
Opera (9.64) behaves as I would expect, deleting the session cookies upon exit whether set as secure or not.
I wanted to be able to rely on this in a web-app I'm working on (having a secure cookie and an insecure cookie as a "logged-in" flag and having them expire together, either with a real time or 0 for a session), but it seems that even if it's in the standard then browsers are not consistent enough to rely on it :/
Is this a bug in the browser, expected behaviour, and/or is the actual lifetime of session cookies not really defined in the standard?
You should never rely on client-side features.
The feature you're working on is usually implemented storing the session ID client-side and the real user info server-side (its ID, whether he's logged in or not, his personal info, etc).
Also bear in mind cookies get sent in every request, so the less you store in a cookie, the better.