Zero redirect login the easy and friendly way

Sun Feb 21 22:00:00 2010

Digg! submit to reddit Delicious RSS Feed Readers

There was a discussion about how to implement login on #catalyst today, so I thought I'd outline my preferred approach, since it doesn't seem to be one people tend to consider.

The basic concept is fairly simple - define a POST prefix that's reserved for the auth system - say '__auth'. Now, in any situation that requires the user to be logged in, we call code that does something like:

  sub check_login :Private {
    my ($self, $c) = @_;

    # if the user is already logged in, we're done
    return 1 if $c->user_exists;

    # look for a login attempt in progress
    my ($user, $pass) = @{$c->req->body_params}{qw(__auth_user __auth_pass)};

    # if we can successfully authenticate

    if ($c->authenticate({ username => $user, password => $pass })) {
      # let the request continue without affecting it
      return 1;
    }

    # if we still have no user, change the template to be our login form
    $c->stash(template => 'login_form');

    # ensure we aren't taken as the normal form of this page
    # (thanks to Aristotle Pagaltzis for pointing this out)

    $c->res->status(403);

    # and abort processing for this request
    $c->detach;
  }

And then our login form starts with:

  <form method="POST" action="">

The advantage of the empty action is that it takes the request URI as-is - you don't have to pass a query parameter around with the old URI (or rely on the referrer header that various "privacy" tools strip - oh joy). For added bonus points you can maintain the body parameters if the previous request was a POST, making any submission except a file upload tend to work.

Why use this rather than other approaches? Well, the other ways I've seen this done generally require a redirect to get back to the right place, which tends to be messy and rules out POST requests entirely (every so often somebody asks how to redirect a POST and this is usually why).

Of course, integrating this with SSL'ed logins if the entire app isn't SSL requires a bit of thought - in this case you may be better providing an https version of the same URL for the initial request and ensuring your templates provide absolute URLs moving the user back to http afterwards.

So, as always, not a perfect solution - but another one to bear in mind when considering how to architect your application.

-- mst, out