maintaining logged in user longer outside of SessionVar

148 views
Skip to first unread message

harryh

unread,
Oct 19, 2009, 2:12:21 PM10/19/09
to Lift
I want users to stay logged into my site for extended periods of time
(through server restarts, and browser restarts). By default Lift
stores a User in a SessionVar so this doesn't get me there. I've
configured jetty so the session cookie doesn't time out for 30 days,
and I have a database table with a session id -> user id mapping, but
keeping this up to date has proven to be kind of a pain as the session
id can change from time to time (like when I restart my servers to
push a new website version) and it's a bit more tricky than I would
like to handle all of this correctly.

Are any other lift users trying to accomplish the same goal? How have
you gone about it? Would it be a good feature for the framework to
have something to do this a bit more "built in"?

-harryh

Ross Mellgren

unread,
Oct 19, 2009, 2:17:01 PM10/19/09
to lif...@googlegroups.com
Why not use a SessionVar that initializes from the database?

object myVar extends SessionVar[MyObj]
(loadValueFromDatabaseOrMakeANewOne)

-Ross

David Pollak

unread,
Oct 19, 2009, 2:23:52 PM10/19/09
to lif...@googlegroups.com
See ProtoExtendedSession

We use it in ESME and it's worked flawlessly for us.  If you need a link to the ESME code (it's Apache 2.0 licensed, so use it all you want, we'll write more), lemme know.
--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

harryh

unread,
Oct 19, 2009, 3:10:38 PM10/19/09
to Lift
> See ProtoExtendedSession

Ah, this is perfect! Just hadn't noticed it before. Thx.

-harryh

Jeppe Nejsum Madsen

unread,
Oct 19, 2009, 3:32:01 PM10/19/09
to lif...@googlegroups.com
harryh <har...@gmail.com> writes:

> I want users to stay logged into my site for extended periods of time
> (through server restarts, and browser restarts). By default Lift
> stores a User in a SessionVar so this doesn't get me there. I've
> configured jetty so the session cookie doesn't time out for 30 days,
> and I have a database table with a session id -> user id mapping,

Instead of using the http session id, you can maintain you own
login-session id and store this in a cookie. I.e.

1) On login, create cookie with id, add mapping id->user to table
2) On logout, clear the cookie, remove mapping from table
3) If you see a request without an http session, but with valid cookie,
lookup the user id in table and autologin the user
4) Periodically, clean table for entries more than 30 days old

/Jeppe

Naftoli Gugenheim

unread,
Oct 19, 2009, 7:52:50 PM10/19/09
to lif...@googlegroups.com
Is it more dangerous to store the user's uniqueId in a cookie than to store another uniqueId that's associated with the user's uniqueId?

-------------------------------------

Naftoli Gugenheim

unread,
Oct 19, 2009, 7:58:29 PM10/19/09
to lif...@googlegroups.com
MetaMegaProtoUser has hooks -- onLogIn, onLogOut, and autologinFunc -- that you can use. autologinFunc is called when loggedIn_? is called and no user is logged in, to give you a chance to log one in.
So you can create a cookie in onLogIn, delete it in onLogOut, and read it in autologinFunc.

harryh

unread,
Oct 19, 2009, 8:02:35 PM10/19/09
to Lift
> Is it more dangerous to store the user's uniqueId in a cookie than to store another uniqueId that's associated with the
> user's uniqueId?

It is if your site has URLs like http://harryh.org/user/[uid]

-harryh

David Pollak

unread,
Oct 19, 2009, 8:10:53 PM10/19/09
to lif...@googlegroups.com
On Mon, Oct 19, 2009 at 5:02 PM, harryh <har...@gmail.com> wrote:

> Is it more dangerous to store the user's uniqueId in a cookie than to store another uniqueId that's associated with the
> user's uniqueId?

An opaque identifier that can be revoked and is not exposed outside of a given user's session is a lot more secure than a global identifier that cannot be revoked or replaced.  For example, it would be possible to cycle the long term session identifier each time it was accessed.  That cannot be done with some sort of unqueId that's associated with the user.  Plus a browser-by-browser identifier is something that can be changed/deleted without impacting the other browsers.
 

It is if your site has URLs like http://harryh.org/user/[uid]

-harryh

harryh

unread,
Oct 19, 2009, 10:04:49 PM10/19/09
to Lift
> See ProtoExtendedSession

It might be kind of annoying to change at this point, but "experation"
is a misspelling in this trait.

-harryh

Naftoli Gugenheim

unread,
Oct 19, 2009, 10:57:05 PM10/19/09
to lif...@googlegroups.com
It shouldn't be such a problem. All that's needed is to rename it, and add a def with the "misspelling" that points to it, deprecated. Something like
@deprecated def experation = expiration
I guess you could file a ticket and someone will eventually get to it.

-------------------------------------

David Pollak

unread,
Oct 20, 2009, 7:01:19 PM10/20/09
to lif...@googlegroups.com
Please file a ticket.

harryh

unread,
Oct 21, 2009, 4:38:32 PM10/21/09
to Lift
> See ProtoExtendedSession

After adding this to Boot.scala:

S.addAround(ExtendedSession.requestLoans)

I'm seeing request to load the User object from the database on every
request (including requests for static flies like images/css/js). Is
there something I can do to make this not happen? Alternately, should
this be considered a Lift bug?

-harryh

David Pollak

unread,
Oct 21, 2009, 5:35:39 PM10/21/09
to lif...@googlegroups.com
It's going to load the user for each stateful request.  I guess we can change it up to make the load lazy so it'll only happen in the requestvar is actually accessed.  Does that sould reasonable?
 

-harryh

harryh

unread,
Oct 21, 2009, 5:44:44 PM10/21/09
to Lift
> It's going to load the user for each stateful request.

What do you mean by a stateful request?

> I guess we can change it up to make the load lazy so it'll only happen in the requestvar is
> actually accessed.  Does that sould reasonable?

If that makes it so I don't hit the database when loading static
files, then yes.

-harryh

David Pollak

unread,
Oct 27, 2009, 11:37:39 PM10/27/09
to lif...@googlegroups.com
btw... all the stuff related to serving css, etc. is done outside of the user session state.  This is in SNAPSHOT.  Please give it a whirl and make sure it's suiting your needs.
 

-harryh

harryh

unread,
Nov 6, 2009, 12:39:50 PM11/6/09
to Lift
> btw... all the stuff related to serving css, etc. is done outside of the
> user session state.  This is in SNAPSHOT.  Please give it a whirl and make
> sure it's suiting your needs.

This is totally my fault for not properly checking before M7, but I'm
still seeing a database access when serving static files (css, images,
js) when using extended sessions.

-harryh

David Pollak

unread,
Nov 6, 2009, 1:54:30 PM11/6/09
to lif...@googlegroups.com

Very, very strange.  Do you have a simple repro case?  If not, I'll see what I can whip up.
 

-harryh


harryh

unread,
Nov 6, 2009, 2:11:44 PM11/6/09
to Lift
> Very, very strange.  Do you have a simple repro case?

No, but I'm not doing anything unusual at all. I've got various
static files (js, gif, css, etc) in src/main/webapp/. In Boot.scala I
have:

S.addAround(ExtendedSession.requestLoans)

ExtendedSession.scala has:

def recoverUserId = User.currentUser match {
case Full(user) => Full(user.userIdAsString)
case _ => Empty
}

Perhaps recoverUserId should be looking at User.curUserId instead of
User.currentUser (which will grab the whole User object from the
database)? I was under the impression that, for static objects, it
wouldn't get this far but perhaps I was mistaken?

-harryh

David Pollak

unread,
Nov 6, 2009, 2:15:40 PM11/6/09
to lif...@googlegroups.com

Yeah, the recoeverUserId should be going against User.curUserId... that'll avoid the loading of the user.

Are you expecting Lift or your servlet container to serve the static files?

Are your static files in well known locations (e.g., /images, /css, etc.)?
 

-harryh

harryh

unread,
Nov 6, 2009, 2:23:38 PM11/6/09
to Lift
> Yeah, the recoeverUserId should be going against User.curUserId... that'll
> avoid the loading of the user.

OK, made this change. Looks better now. Thx!

> Are you expecting Lift or your servlet container to serve the static files?

Lift (well, I don't really care I suppose, but it's Lift right now).

> Are your static files in well known locations (e.g., /images, /css, etc.)?

Some are, but not all of them. I could do some reorganization in this
area if necessary.

-harryh

David Pollak

unread,
Nov 6, 2009, 2:35:14 PM11/6/09
to lif...@googlegroups.com
On Fri, Nov 6, 2009 at 11:23 AM, harryh <har...@gmail.com> wrote:

> Yeah, the recoeverUserId should be going against User.curUserId... that'll
> avoid the loading of the user.

OK, made this change. Looks better now.  Thx!

> Are you expecting Lift or your servlet container to serve the static files?

Lift (well, I don't really care I suppose, but it's Lift right now).

try putting the following in Boot.scala:

LiftRules.liftRequest.append {
  case Req(_, "css", GetRequest) => false
  case Req(_, "js", GetRequest) => false
}

This will forward requests for css and js files to the container very early in the request cycle.

Also, you might want to make recoverUserId a RequestVar:

object recoverUserId extends RequestVar(User.currentUser.map(_.userIdAsString))

The RequestVar will only be calculated on access (it's lazy on a request-by-request basis).
 

> Are your static files in well known locations (e.g., /images, /css, etc.)?

Some are, but not all of them.  I could do some reorganization in this
area if necessary.

-harryh

Jeppe Nejsum Madsen

unread,
Nov 6, 2009, 2:39:19 PM11/6/09
to lif...@googlegroups.com
harryh <har...@gmail.com> writes:

Hmm,, this must be something with extended sessions. This is some log
output from my app:

20:36:49.941 [qtp-963293928-4] INFO bootstrap.liftweb.DBVendor$ - Found connection in pool, size=1
20:36:49.942 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$ - Verifying DB connection:org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:49.955 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$ - DB connection ok: org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:49.961 [qtp-963293928-4] INFO query - >>> All queries took 5ms:
20:36:49.961 [qtp-963293928-4] INFO query - Exec query "SELECT users.id, users.firstname, users.lastname, users.email, users.locale, users.timezone, users.password_pw, users.password_slt, users.account_id, users.superuser, users.uniqueid, users.validated FROM users WHERE id = 2" : org.postgresql.jdbc4.Jdbc4ResultSet@34374ed5 took 3ms
20:36:49.961 [qtp-963293928-4] INFO query - <<< End queries
20:36:50.003 [qtp-963293928-4] INFO query - >>> All queries took 5ms:
20:36:50.006 [qtp-963293928-4] INFO query - Exec query "SELECT accounts.name, accounts.id FROM accounts WHERE id = 1" : org.postgresql.jdbc4.Jdbc4ResultSet@78d9ab8 took 5ms
20:36:50.006 [qtp-963293928-4] INFO query - <<< End queries
20:36:50.059 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$ - Releasing connection, size=1: org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:50.062 [qtp-963293928-4] INFO lift - Service request (GET) / took 159 Milliseconds
20:36:50.111 [qtp-963293928-5] INFO lift - Service request (GET) /classpath/blueprint/print.css took 15 Milliseconds

Note that no db connection is acquired for blueprint.css (previous
versions would get one)

/Jeppe

David Pollak

unread,
Nov 6, 2009, 2:45:51 PM11/6/09
to lif...@googlegroups.com
On Fri, Nov 6, 2009 at 11:39 AM, Jeppe Nejsum Madsen <je...@ingolfs.dk> wrote:

harryh <har...@gmail.com> writes:

>> btw... all the stuff related to serving css, etc. is done outside of the
>> user session state.  This is in SNAPSHOT.  Please give it a whirl and make
>> sure it's suiting your needs.
>
> This is totally my fault for not properly checking before M7, but I'm
> still seeing a database access when serving static files (css, images,
> js) when using extended sessions.

Hmm,,  this must be something with extended sessions.

This is for resources not served via classpath.  Basically, Lift has to go through and check to see if there's any handler for the particular request path before deciding not to handle it or to handle it by serving a static resource.  All of this means creating a session context.

The way to deal is to explicitly tell Lift that certain resources are not in fact to be served under any conditions.
 
This is some log
output from my app:

20:36:49.941 [qtp-963293928-4] INFO  bootstrap.liftweb.DBVendor$          - Found connection in pool, size=1
20:36:49.942 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$          - Verifying DB connection:org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:49.955 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$          - DB connection ok: org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:49.961 [qtp-963293928-4] INFO  query                                - >>> All queries took 5ms:
20:36:49.961 [qtp-963293928-4] INFO  query                                -     Exec query "SELECT users.id, users.firstname, users.lastname, users.email, users.locale, users.timezone, users.password_pw, users.password_slt, users.account_id, users.superuser, users.uniqueid, users.validated FROM users WHERE id = 2" : org.postgresql.jdbc4.Jdbc4ResultSet@34374ed5 took 3ms
20:36:49.961 [qtp-963293928-4] INFO  query                                - <<< End queries
20:36:50.003 [qtp-963293928-4] INFO  query                                - >>> All queries took 5ms:
20:36:50.006 [qtp-963293928-4] INFO  query                                -     Exec query "SELECT accounts.name, accounts.id FROM accounts WHERE id = 1" : org.postgresql.jdbc4.Jdbc4ResultSet@78d9ab8 took 5ms
20:36:50.006 [qtp-963293928-4] INFO  query                                - <<< End queries
20:36:50.059 [qtp-963293928-4] TRACE bootstrap.liftweb.DBVendor$          - Releasing connection, size=1: org.postgresql.jdbc4.Jdbc4Connection@63376afa
20:36:50.062 [qtp-963293928-4] INFO  lift                                 - Service request (GET) / took 159 Milliseconds
20:36:50.111 [qtp-963293928-5] INFO  lift                                 - Service request (GET) /classpath/blueprint/print.css took 15 Milliseconds

Note that no db connection is acquired for blueprint.css (previous
versions would get one)

/Jeppe


harryh

unread,
Nov 6, 2009, 2:46:05 PM11/6/09
to Lift
> LiftRules.liftRequest.append {
>   case Req(_, "css", GetRequest) => false
>   case Req(_, "js", GetRequest) => false
> }

Oh nifty. Though I'll need to make a slight change so that things
under /classpath (like /classpath/jquery.js) are handled by Lift.

-harryh

David Pollak

unread,
Nov 6, 2009, 2:48:35 PM11/6/09
to lif...@googlegroups.com

Yep, that's right... so:

> LiftRules.liftRequest.append {
      case Req("classpath" :: _, _, _) => true

>   case Req(_, "css", GetRequest) => false
>   case Req(_, "js", GetRequest) => false
> }
 

-harryh

Reply all
Reply to author
Forward
0 new messages