I'm using Rack to try to implement "Remember Me" functionality in my Sinatra app.
I'm able to set the session cookie to expire when the session ends or in X seconds time but I'd like to do both.
For example, if a user has clicked "remember me" then I wish for their session to end after X seconds. Eg, my app.rb has a line that looks like this:
use Rack::Session::Cookie, :expire_after => 2592000, #30 days in seconds
:secret => MY_SECRET
I've tried to do the following when the user logs in:
if (!remember_me)
env['rack.session.options'][:expire_after] = nil
end
However, this does not set the cookie value.
How to set this?
I was trying to do the exact same thing and I figured out what the problem for me was. The session cookie gets set on every request if you have an expire_after time set. So when you say if (!remember_me), for that request the cookie's expire time gets set to nil. However, on the very next request, the session cookie is reinitialized with an expire time of 2592000. It seems the fix is to not set a default expire_after time and instead say:
# don't set default expire time
use Rack::Session::Cookie, :secret => MY_SECRET
if(remember_me)
env['rack.session.options'][:expire_after] = 2592000
end
I have unfortunately not figured out how to have a default expire_after time and to permanently extend that time programatically.
This probably has to be done before the session is loaded.
See Rack::Session::Cookie#load_session and Rack::Session::Cookie#commit_session
Chris' answer actually didn't work for me. I found that I had to make sure that I included the original session 'options' with the new 'expire_after' value, so instead of:
env['rack.session.options'][:expire_after] = 2592000
I would use:
env['rack.session.options'].merge! expire_after: 2592000
and be sure to put the use Rack::Session::Cookie statement (without an expire_after setting) in you configure block, if you are using Sinatra.
This did the trick.
Related
When a user logs into our site, a cookie gets set like so:
$this->input->set_cookie(self::AUTH_COOKIE, 'v1='.$var1.'&v2='.$var2, '1814400', ".oursite.com", "/", "", FALSE);
When they log out, the cookie is 'unset' like so:
----- UPDATED -----
$carray = array(
'name' => self::AUTH_COOKIE,
'value' => null,
'expire' => time() - 86500,
'domain' => '.oursite.com',
'path' => '/',
'prefix' => '',
'secure' => FALSE
);
$this->input->set_cookie($carray);
delete_cookie(self::AUTH_COOKIE, ".oursite.com", "/", "");
----- END UPDATE -----
This works fine on most browser/platform combinations, but in Firefox 26 on Windows 7 SP1, the cookie never gets updated. It retains the data that was set when it was created. Any idea why this approach would not work in Firefox on Windows?
*----- Additional update - this no longer works on Firefox on my Mac and also fails on Internet Explorer. Haven't tested Safari or Chrome.
cfr. http://ellislab.com/codeigniter/user-guide/libraries/input.html:
To delete a cookie set it with the expiration blank.
For site-wide cookies regardless of how your site is requested, add your URL to the domain starting with a period, like this: .your-domain.com
the dot "." is missing in front of oursite.com:
.oursite.com
I think this last thing might be causing it.
If all else fails try with native PHP cookies to see whether that works.
I finally solved this with a potentially 3-part solution. Honestly I do not know whether steps 1 & 2 are necessary as they did not work on their own, but I do not wish to mess with my code now that it seems to be working.
I changed the expire value of the cookie array to null.
I unset a second cookie called sap_r that was set if the user had clicked 'Remember Me' when logging in.
My original code contained an ajax call immediately after setting the cookie values to null. I switched the order so that the ajax is called first and then the cookies are 'unset'.
I tested after each of these and it was after this final step that the cookies were successfully removed. So - I do not know if some combination of the three solved it or only the last one. If I get a chance to test further, I will comment here.
You can set a session expiry for a Sinatra app when you set up the session engine:
use Rack::Session::Cookie, :expire_after => 60*60*3, :secret => 'xxxx'
But I want to enable a longer session for certain users. Say two weeks.
session[:expires] = ?? #how and where do I put this.?
Do I need to set on each call (before do?) or is once good enough? Is session[:expires] the right thing to set?
First make sure you don't set an expire_after value with the use Rack::Session::Cookie command, then put use Rack::Session::Cookie in your configure block. Next create an "expiry time" variable (let's say expiry_time for this example) or setting in config. Now for each user, when they login, retrieve their expiry_time setting and issue the following command:
env['rack.session.options'].merge! expire_after: expiry_time
That should do what you are asking.
If this doesn't work for you, try putting the env...merge! command in a before block.
I tried to do this via an after filter in Sinatra but it didn't work, I guess it sets the session after after filters have run, so I knocked up a quick Rack filter and it appears to work.
require 'sinatra'
class SessionExpiryModifier
def initialize(app,options={})
#app,#options = app,options
end
def call(env)
warn env["rack.session.options"].inspect
t = Time.now.to_i.even? ? 10 : 60
env["rack.session.options"].merge! :expire_after => 60 * 60 * t
#app.call env
end
end
configure do
use Rack::Session::Cookie,
:expire_after => 60*60*3,
:secret => 'xxxx' * 10
use SessionExpiryModifier
end
get "/" do
session[:usr] = Time.now
env["rack.session.options"].inspect
end
However, that makes it a lot harder to get a conditional from the Sinatra app into the Rack filter to decide on which branch to take, but that depends on what your condition is. Perhaps inject something into the headers that the filter can read to make the decision.
Suppose I want to delete a cookie (for example, ring's session cookie):
Making a response map like this:
{:cookies {"ring-session" {:value "kill", :max-age 1}}}
seems to work, but it feels a bit hacky.
Is there a clean way to just delete it?
That seems like quite a reasonable way of going about it. Many web a applications delete cookies be replacing them with one that is about to expire. The :max-age 1 syntax makes this look much more elegant than it does in, for example, Javascript.
I was using wrap-session and the other handlers in ring.middleware.
Setting the cookie to max age was not working in the response since it was just being overwritten (very frustrating to diagnose!)
This is what I needed to do:
(defn clear-session! [resp]
(assoc resp :session nil))
source
ringseems do not support this, but you can send the user agent a new cookie with an Expires attribute with a value in the past.
more info
If you are using ring-session, this could be another way to do delete cookie.
(def epoch (ZonedDateTime/ofInstant Instant/EPOCH ZoneOffset/UTC))
(def response {:status 200,
:body "{\"message\":\"ok\"}",
:session nil,
:session-cookie-attrs {:expires epoch}})
I have a Sinatra app running on Heroku. I've had a really tough time properly setting up the cookies for my users. I've been looking at examples and documentation for a couple days now and just can't seem to crack the problem. I would like to have my users login with a email and password. If the login is successful, I want to create a cookie which expires after some set amount of time. I want the user to remain logged in if they close and re-open their browser or if I push new code to Heroku. I think that is just normal use of cookies, so if someone can help me get it going, I'd be very grateful.
Here is how I think I'm supposed to setup the post '/login' route.
post '/login/?' do
#do_login is a helper method which checks if the user's credentials are correct
if do_login
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'www.Feistie.com',
:path => '/',
:expire_after => 2592000,
:secret => 'a_30_character_key',
:user_id => params[:user_id]
end
end
Even if that is correct, I don't really understand it. What is the :key? I'm assuming :domain should just be set to my website as I did. What about :path? Why is :path set to '/'? Is there a place I actually set the secret key for my Rack app? I'm not totally clear on how cookie security works. I'm also not sure if I can add :user_id to the hash the way I did.
Then next part is setting up a helper "logged_in?" to see if someone is logged in?
def logged_in?
!session.nil?
end
That is probably wrong, but I figured I'd try.
I also need to be able to check who the current user actually is. I used to do this by session[:user_id] == #user.id or something similar. The main question I have is how do I access the :user_id value in the cookie?
Finally the logout.
post '/logout/?' do
session.clear
end
If you guys could get me on track with all this, it would be amazing! Thanks in advance and if you need more info, I will be glad to provide.
It's much simpler than that.
http://www.sinatrarb.com/faq.html#sessions
enable :sessions
post '/login/?' do
if do_login
session[:user_id] = params[:user_id]
end
end
post '/logout/?' do
session[:user_id] = nil
end
I generally try to keep session management to the business logic in get, post, before, etc blocks. You can use a before filter to "log-in" the current session's user.
before '*' do
set :user_id, session[:user_id]
end
This is completely optional, but you can put any cookie config in your rackup file:
use Rack::Session::Cookie,
:key => 'rack.session',
:path => '/',
:expire_after => 14400, # In seconds
:secret => 'change_me'
You could just try setting it with response
response.set_cookie "user_id", {:value => params['user_id'],
:expires => (Time.now + 52*7*24*60*60)}
And you can read it back any time before it expires with
request.cookies["user_id"]
To delete the cookie
response.delete_cookie "user_id"
...
How can I make sure a session for a client on my express.js app lasts longer than a few hours? I don't want them to log in anew every time they visit my site.
I tried to fiddle with the expires option on the express.session method, but that doesn't seem to have any effect.
You can use req.session.cookie.expires = aSpecificDateAndTime; or req.session.cookie.maxAge = aNumberOfMilliseconds;. Both are equivalent, according to the docs.
Forgot to mention, you can also set these options when setting the middleware (i.e. express.session({secret: "whatever", cookie: {maxAge: 60000}});
cookie: {
path: '/'
,expires: false
,httpOnly: true
,domain:'.example.com'
}
Above setting gives you to have session alive until browser closes.
Available among all pages ( path set to root ).