Implementing the remember me feature

The remember me feature allows users to automatically authenticate themselves with the web application even after they have closed the browser, or the HTTP session has been destroyed, without having to enter their usernames and passwords. If a user has previously authenticated themselves and chose to be remembered, the web application will, remember the user using HTTP cookies.

Essentially, with the remember me feature, your application can consume two kinds of login credentials:

  • A username and password combination
  • A valid HTTP cookie previously created by the web application

Let's think of the login/logout process, putting the remember me functionality into play this time. When a user requests the web application for the first time, the VaadinUI.init method is invoked. This method will check whether the user is authenticated or not in order to show the corresponding UI component. This is delegated to the AuthService class in our example. The AuthService.isAuthenticated method checks whether or not there's an authenticated user in the HTTP session. At firstthere is none, so it should check whether the user was remembered before. Ignoring the details, we know the user was not remembered before. So the PublicComponent is shown and the user can log in with, username and password. But this time, the user checks the Remember me checkbox.

We need to tell this choice to the AuthService.authenticate method (by passing a Boolean value from the checkbox), which in turn will check if the username and password are correct and, if so, perform the logic to remember the user. This is the interesting part.

A user is remembered by creating an HTTP cookie with the name, say remember-me, and storing a value that allows us to identify the user later. We could be tempted to simply store the plain username in this cookie, but that would lead to a serious security issue; if a malicious user has access to the browser and gets the value of a remember-me cookie, they will be able to sign in as that user by simply creating a cookie with the stolen value.

Instead of storing sensitive information in the cookie, we can store a randomly generated string and store the username in the server using a Java Map, where the key is the random string and the value is the username.

Using a Java Map is good enough for the example in this chapter. However, keep in mind that if you restart the server, remembered users are no longer remembered (pun intended). A real-life application should use a persistent Map, such as an SQL table, but the principle is exactly the same. Additionally, you might want to store a hash of the random key, in the same way as you should do with user passwords. This will protect users if data in this table is compromised.

So, let's recap. The user logged in by providing their username and password and checking the Remember me option, and the web application created a cookie containing a random key and stored the username in a Map using that key. Now, let's see what happens when the user closes the browser (or waits until the HTTP session is closed) and requests the web application again.

As usual, the VaadinUI.init method is invoked and the AuthService.isAuthenticated method checks whether there's an authenticated user in the HTTP session. Of coursethere isn't, and it proceeds with the cookie check. This time, there is a remember-me cookie, so the method just searches for the username in the Map of remembered users and gets the value of the username. Now, it should just store the username in the HTTP session and return true. The user was automatically authenticated!

The last part we need to consider is the logout action. When the user logs out, the remember-me cookie should be destroyed, along with the corresponding entry in the Java Map of remembered users.

I would urge you to try and implement all this by yourself. I have created a branch with the name remember-me-exercise in the source code that accompanies this book. You can use this branch as a starting point if you want to do the exercise. You can check it out by running:

    cd Data-centric-Applications-with-Vaadin-8
    git checkout remember-me-exercise

If you prefer to see the solution, just check the code in the master branch.

Let's see some snippets of code you could use for the exercise. Let's begin with HTTP cookies management. You can send a new cookie to the browser by using the VaadinRequest.addCookie method. The following snippet of code creates a new cookie with the name remember-me and the value admin and sends it to the browser:

Cookie cookie = new Cookie("remember-me", "admin"); 
cookie.setPath("/"); 
cookie.setMaxAge(60 * 60 * 24 * 15); 
VaadinService.getCurrentResponse().addCookie(cookie); 

The setPath defines the path for the cookie. The browser sends the cookies associated with that path in subsequent requests to the server.

Note that the path should include the servlet's context path. You can get it by calling VaadinServlet.getCurrent().getServletContext().getContextPath().

The setMaxAge method allows you to set the time for which the cookie will be valid. The time is given in seconds, which means that the previous snippet creates a cookie valid for 15 days.

To delete a cookie, set its age to zero. For example, the following code removes the remember-me cookie:

Cookie cookie = new Cookie("remember-me", ""); 
cookie.setPath("/"); 
cookie.setMaxAge(0); 
VaadinService.getCurrentResponse().addCookie(cookie); 

You can get all the cookies reported by the browser by using the VaadinRequest.getCookies method. You can get an instance of VaadinRequest via VaadinService.getCurrent(). The following snippet of code retrieves an Optional of a cookie with the name remember-me:

Cookie[] cookies = VaadinService.getCurrentRequest().getCookies(); 
 
Optional<Cookie> cookie = Arrays.stream(cookies) 
        .filter(c -> "remember-me".equals(c.getName())) 
        .findFirst(); 

Finally, here there's a tip to generate a random string suitable for the Map of remembered users:

SecureRandom random = new SecureRandom(); 
String randomKey = new BigInteger(130, random).toString(32); 

In short, this converts a randomly generated BigInteger consisting of 130 bits and converts them into a sequence of base-32 characters. Although 128 bits is secure enough, a base-32 character can take five bits. 128/5 = 25.6, so we need a couple of extra bits to get the next multiple of 5, which leads to 130/5=26. In conclusion, we get 26 random characters. Keep in mind that UUIDs are not designed to be unpredictable and should not be used to identify sessions.

A good implementation should also periodically clean the Map of remembered users. This can be achieved by adding a custom data type that stores not only the username but the expiry date. A background process can run every day, checking for expired entries and removing them from the Map.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset