How to force Jetty to ask for credentials with BASIC authentication after invalidating the session?

I'm using jetty 6.1.22 with BASIC authentication as my login mechanism. The first time I log into the web app, the browser requests the username and password.

If it try to log out using a session.invalidate(), the session is invalidated but the credentials are cached. This means that if I try to connect to a secured URL, I will see a different session id but no dialog for username and password.


Solution 1:

(I realise this question is old, but it's something that other people might want an answer to)

BASIC Authentication doesn't really allow what you're asking for, but you can get something that works a bit like what you want, if you're willing to live with some "quirks".

BASIC Authentication has 2 aspects that make it hard to control in this way

  • It is stateless
  • It is client-side

Both of those aspects are features, but they make it hard to link BASIC authentication to Java sessions. That is why alternate login mechanisms (like Java FORM login exist)

BASIC Authentication is stateless
That means that the client has absolutely no idea what is going on on the server-side, and it certainly has no way of linking a set of BASIC Authentication credentials to a cookie (which is what mostly controls the java session)
What happens in that the browser will simply send the username+password with every request, until it decides to stop (generally because the browser was closed, and a new browser session was created).
The browser doesn't know what the server is doing with the credentials. It doesn't know whether they're needed any more, or not, and it doesn't know when the server side has decided that a "new session" has started, it just keeps on sending that username+password with each request.

BASIC Authentication is Client-Side
That dialog for the user/password is handled by the client. All the server says is "The URL you requested needs a username+password, and we've named this security realm 'xyz' ".
The server doesn't know whether the user is typing in a password each time, or whether the client has cached them. It doesn't know if there really is a user there, or whether the password was pulled out of a file.
The only thing the server can do is say "You need to give me a user+password before accessing this URL"

How to fake it
Essentially, you need to detect (i.e. make an educated guess) when the browser is sending old credentials and then send the "Please give me a user+password" response (HTTP 401) again.

The simplest way to do that is to have a filter in your application that detects the first time the user has logged in for that session, and sends a 401 response code. Something like:

    if(session.getAttribute("auth") == null)
    {
        response.setStatus(401);
        response.setHeader("WWW-Authenticate", "basic realm=\"Auth (" + session.getCreationTime() + ")\"" );
        session.setAttribute("auth", Boolean.TRUE);
        writer.println("Login Required");
        return;
    }

In that example I've named the realm with the creation time of the session (although it's not very pretty - you'd probably want to format it differently). That means that the name of the security realm changes each time you invalidate the session, which helps prevent the client from getting confused (why is it asking me for a user+password again, for the same realm, when I just gave one?).
The new realm name will not confuse the servlet container, because it will never see it - the client response does not include the realm name.

The trick though, it that you don't want the user to get asked for their password twice the first time they logon. And, out of the box, this solution will do that - once for the container to ask for it, and then again when your filter does.

How to avoid getting 2 login boxes There are 4 options.

  1. Do it all in code.
  2. Use a secondary session cookie to try and tell whether the user is logging in for the first time.
  3. Have your root URL be unsecured (but have it do nothing)
  4. Have all your app be unsecured, except for 1 page, and redirect users to that page if they are not logged in.

Do it in code

If you're happy to do all of the security inside your own servlet/filter, and get no help from the servlet container (Jetty) then that's not too hard. You simply turn off BASIC auth in your web.xml, and do it all in code. (There's a fair bit of work, and you need to make sure you don't leave any security holes open, but it's conceptually quite easy)
Most people don't want to do that.

Secondary Cookies
After a user logins in to your application, you can set a cookie ("authenticated") that expires at the end of the browser session. This cookie is tied to the browser session and not to the Java session.
When you invalidate the Java session - do not invalidate the "authenticated" cookie.
If a user logs in to a fresh java session (i.e. session.getAttribute("auth")==null), but still has this "authenticated" then you know that they're re-using an existing browser session and are probably re-using existing HTTP authentication credentials. In that case you do the 401 trick to force them to give new credentials.

Unsecured root URL
If your root URL is unsecured, and you know that this is the URL that your users always login to, then you can simply put your "auth"/401 check at that URL, and it will solve the problem. But make sure you don't accidentally open a security hole.
This URL should not have any functionality other than redirecting users to the "real" app (that is secured)

Single secured URL
Have 1 URL that is secured (e.g. "/login").
As well as your "auth"/401 filter, have another filter (or additional code in the same filter) on all pages (other than login) that checks whether request.getUserPrincipal() is set. If not, redirect the user to "/login".

The much easier solution
Just use a FORM login method instead of BASIC authentication. It's designed to solve this problem.