Handling login/logout correctly

Ovid on 2009-08-19T21:01:39

One thing frustrating about many Web sites is that login and logout often return you to the front page or keep you on the login or logout page. I hate that. You want to remain where you instead of navigating back to that. I've solved that with my Web app with Catalyst::Plugin::Session. It has a nifty 'flash' method. This is like the stash, except it's persistent across one request. Thus, in my Escape::Controller::Root 'auto' method, the method called before the others, I have this:

# 'none' is from Perl6::Junction
if ( $c->req->path eq none(qw/login logout/) ) { 
    $c->flash->{path} = $c->req->uri->as_string; 
} 
$c->keep_flash('path');

With that, if you're not logging in or logging out, the flash path is the URI of where you were last at. My login/logout controllers automatically redirect you to the flashed path, thus keeping you where you were.

$c->response->redirect( $c->flash->{path} || $c->uri_for('/') );

The only tricky bit is that flash only keeps flash info which has changed since the last request. The keep_flash line keeps the path across multiple request.

I use the full URI in case someone is on a page like /country/?starts_with=m. My opinion is that every page on a Web site should respond sensibly to a GET request. If you hold to that, the above strategy seems sensible. Better strategies welcome :)


All shiny and reasonable

phaylon on 2009-08-19T22:14:25

I'd just check $c->action (or $c->action->reverse) instead of the path, in case you reconfigure it one day to another path. The rest is very nice :)

in sub end

fayland on 2009-08-19T23:07:18

personal I do something like in sub end



if ( $c->res->location ) {
if ( $c->res->location eq '/login' ) {
$c->flash->{path} = $c->req->uri->as_string;
}
}



so that you don't need write session every request.

Why put state on the server?

Aristotle on 2009-08-20T07:16:39

Just have the logout action accept a returnto query parameter, and stick the value of $c->req->uri into that when you emit the logout link. Much simpler; the server doesn’t have to do any bookkeeping, no DB accesses, nothing.

Also, flash is bad anyway – subject to race conditions. It will restore the flashed variables into the session for whichever request happens to come in with the right session cookie, even if that request was made by a different tab/browser window/computer/scraper thread.

In your case, these potential problems of flash are unlikely edge cases, but this is not always so – and the 100% reliable solution is actually simpler and more scalable.

Generally, if the answer is session data of any form, you’re asking the wrong question.

Re:Why put state on the server?

mpeters on 2009-08-20T13:20:02

I usually do something similar if I want to go to some other page after login/logout, but you don't need to prepopulate the login/logout links just use the REFERER header.

Re:Why put state on the server?

phaylon on 2009-08-20T13:37:07

The referer is rather unreliable and can be, and is often, changed by the browser or a proxy.

Re:Why put state on the server?

tgape on 2009-08-22T02:54:41

Agreed.  In any event, flash-based solutions are unlikely to win you friends who are truly security conscious when there are static methods that have worked forever that still work.  (And, for that matter, shockwave, java, or javascript solutions will also not motivate friends in the above mentioned crowd.)

Passing the information as form values is especially a good idea because it then makes passing other form values a no brainer.  And when you do that, the login form no longer causes your users to curse at you for requiring that they re-enter their form data.  I've never seen a client-side code solution to return you to the same place handle this right.

Finally, it also has a win for the multi-tab or multi-window user; if they're on your site multiple times, and they manage to get two login pages displayed, both pages will then proceed to the correct place.  Yeah, I know, I'm a freak for doing this, but sometimes my net connection's slow.  Rather than waiting, I change tabs.  Isn't that what they're for?

Re:Why put state on the server?

Aristotle on 2009-08-22T04:28:52

Err, “flash” in this case does not refer to Macromedia Flash, rather, to a particular feature of the session plugin.

Re:Why put state on the server?

tgape on 2009-08-22T19:51:51

Sigh.  That's what happens when I try to comment on things while trying to catch up on my reading.  If it had been using Macromedia Flash, then it wouldn't be storing the data server-side.

However, I do think my point still stands of this mentality tends to drops post form data.  It doesn't have to - one could store that in session data also, but it's considerably easier and less problematic to simply use forms, as indicated for the more general case in your original response.

Backwards?

frew on 2009-08-20T13:37:16

It seems like you got what flash and stash are backwards, from the docs in the Session plugin. You say flash lasts for one request. This is what the doc for it says:

"Think of it as a stash that lasts for longer than one request, letting you redirect instead of forward."

Am I misunderstanding?

Re:Backwards?

Aristotle on 2009-08-20T13:49:32

The stash lasts for the duration of a single request.

The flash persists after the request and becomes part of the stash in the next request – but only once (unless you persist the data again).

The terminology is kinda confusing, I guess.

Re:Backwards?

Ovid on 2009-08-20T14:40:37

Actually, it's not quite "persist the data again". Flash is only preserved if the data changes. Thus, if you need to preserve the same data across multiple requests, you need to use the keep_flash() method.