Blog

Implementing 'remember me' with Vaadin

By  
Alejandro Duarte
Alejandro Duarte
·
On Jan 19, 2017 10:17:00 AM
·

One of the most common features for a login form is the remember me feature that allows users to be automatically signed in from a certain machine even after the HTTP session expires:

Screen Shot 2017-01-04 at 0.31.39.png

Web applications usually implement this feature by using cookies. When the user selects the remember me option and signs in, the application stores a cookie to identify the user. The next time the user requests the web application, the standard authentication process is skipped and the user is automatically logged in.

Never store sensitive information in cookies. It’s not safe to store, for example, the ID of the user. If an attacker knows a valid user ID, they can create a new cookie containing such ID and sign in as the impersonated user. In this example, I’m going to store a random string and associate it with a username in a HashMap. In real-world applications, you can use an SQL database, for example, and store not only a random ID, but the hash of a token similarly to what you should do with the user’s passwords.

Implementing the business logic

Let’s see how to do this with Vaadin 8. The first step is to implement a UserService class (or a UserRepository class) to encapsulate any logic related to user data persistence. This UserService class should provide methods to:

  • Authenticate the user (username and password check).

  • Store a remembered user.

  • Get a remembered user.

  • Remove a remembered user.

A remembered user in this example is a random id:username pair. The following is an example implementation of the UserService class:

public class UserService {

    private static SecureRandom random = new SecureRandom();

    private static Map<String, String> rememberedUsers
            = new HashMap<>();

    public static boolean isAuthenticUser(String username,
            String password) {
        return username.equals("admin")
                && password.equals("password");
    }

    public static String rememberUser(String username) {
        String randomId = new BigInteger(130, random).toString(32);
        rememberedUsers.put(randomId, username);
        return randomId;
    }

    public static String getRememberedUser(String id) {
        return rememberedUsers.get(id);
    }

    public static void removeRememberedUser(String id) {
        rememberedUsers.remove(id);
    }
}

Notice that for simplicity reasons, there’s no database connection and the isAuthentic method has only one hardcoded user (admin/password). You should implement your own database related logic depending on your specific persistence technologies. Notice also that the remembered users are stored in memory using a HashMap. Here again, you should use a database and implement the corresponding logic to persist and delete data.

Implementing the web-related logic

With a UserService like the previous one, you can implement the web related logic for authentication, that is, handling values in the HTTP session and cookies. This class should provide methods to:

  • Check whether a user is currently authenticated, either by the standard authentication mechanism or by the remember me functionality.

  • Login a certain user by credentials.

  • Logout the current user.

Of course, this class delegates user data manipulation to the UserService class. The following is an example implementation:

public class AuthService {

    private static final String COOKIE_NAME = "remember-me";
    public static final String SESSION_USERNAME = "username";

    public static boolean isAuthenticated() {
        return VaadinSession.getCurrent()
                .getAttribute(SESSION_USERNAME) != null
                        || loginRememberedUser();
    }

    public static boolean login(String username, String password,
            boolean rememberMe) {
        if (UserService.isAuthenticUser(username, password)) {
            VaadinSession.getCurrent().setAttribute(
                    SESSION_USERNAME, username);

            if (rememberMe) {
                rememberUser(username);
            }
            return true;
        }

        return false;
    }

    public static void logOut() {
        Optional<Cookie> cookie = getRememberMeCookie();
        if (cookie.isPresent()) {
            String id = cookie.get().getValue();
            UserService.removeRememberedUser(id);
            deleteRememberMeCookie();
        }

        VaadinSession.getCurrent().close();
        Page.getCurrent().setLocation("");
    }

    private static Optional<Cookie> getRememberMeCookie() {
        Cookie[] cookies =
                VaadinService.getCurrentRequest().getCookies();
        return Arrays.stream(cookies)
                .filter(c -> c.getName().equals(COOKIE_NAME))
                .findFirst();
    }

    private static boolean loginRememberedUser() {
        Optional<Cookie> rememberMeCookie = getRememberMeCookie();

        if (rememberMeCookie.isPresent()) {
            String id = rememberMeCookie.get().getValue();
            String username = UserService.getRememberedUser(id);

            if (username != null) {
                VaadinSession.getCurrent()
                        .setAttribute(SESSION_USERNAME, username);
                return true;
            }
        }

        return false;
    }

    private static void rememberUser(String username) {
        String id = UserService.rememberUser(username);

        Cookie cookie = new Cookie(COOKIE_NAME, id);
        cookie.setPath("/");
        cookie.setMaxAge(60 * 60 * 24 * 30); // valid for 30 days
        VaadinService.getCurrentResponse().addCookie(cookie);
    }

    private static void deleteRememberMeCookie() {
        Cookie cookie = new Cookie(COOKIE_NAME, "");
        cookie.setPath("/");
        cookie.setMaxAge(0);
        VaadinService.getCurrentResponse().addCookie(cookie);
    }
}

A user is considered authenticated if there’s an attribute in the HTTP session or if a cookie is present and valid. Notice how the login method accepts not only the credentials (username and password), but a flag indicating if the user should be remembered. Notice also how the cookie is removed when the user is logged out.

Implementing the UI

Implementing a Vaadin UI that uses this class is pretty straightforward. The simplest application to test this behavior should contain at least two “screens” or components. A public component (the login form) and a private component (the actual functionality of the application).

The Vaadin UI implementation couldn't be simpler:

public class VaadinUI extends UI {

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        if (!AuthService.isAuthenticated()) {
            showPublicComponent();
        } else {
            showPrivateComponent();
        }
    }

    public void showPublicComponent() {
        setContent(new PublicComponent());
    }

    public void showPrivateComponent() {
        setContent(new PrivateComponent());
    }
}

The public component looks like this:

Screen Shot 2017-01-04 at 0.31.39.png

The interesting part of the implementation is the button’s ClickListener:

public class PublicComponent extends CustomComponent {

    public PublicComponent() {
        ...
        Button button = new Button(
                "Login",
                e -> onLogin(
                    username.getValue(),
                    password.getValue(),
                    rememberMe.getValue())
        );
        ...
    }

    private void onLogin(String username, String password,
            boolean rememberMe) {
        if (AuthService.login(username, password, rememberMe)) {
            VaadinUI ui = (VaadinUI) UI.getCurrent();
            ui.showPrivateComponent();
        } else {
            Notification.show(
                "Invalid credentials (for demo use: admin/password)",
                Notification.Type.ERROR_MESSAGE
            );
        }
    }
}

As you can see, the onLogin method uses the AuthService.login method to check if the user is authentic.

The private component is pretty simple as well. It shows a message with the username and a button to sign out:

Screen Shot 2017-01-04 at 0.31.49.png

The following is the full implementation:

public class PrivateComponent extends CustomComponent {

    public PrivateComponent() {
        String username = (String) VaadinSession.getCurrent()
                .getAttribute(AuthService.SESSION_USERNAME);

        Label label = new Label("Welcome, " + username);
        label.addStyleName(ValoTheme.LABEL_HUGE);

        Button button = new Button("Sign out", this::onLogout);

        setCompositionRoot(new VerticalLayout(label, button));
    }

    private void onLogout(Button.ClickEvent event) {
        AuthService.logOut();
    }
}

Nothing special about it. The username is read from the HTTP session and the logout button invokes the AuthService.logOut() method that removes the remember me cookie and invalidates the HTTP session.

You can find the full source code of this example on GitHub

Alejandro Duarte
Alejandro Duarte
Software Engineer and Developer Advocate at MariaDB Corporation. Author of Practical Vaadin (Apress), Data-Centric Applications with Vaadin 8 (Packt), and Vaadin 7 UI Design by Example (Packt). Passionate about software development with Java and open-source technologies. Contact him on Twitter @alejandro_du or through his personal blog at www.programmingbrain.com.
Other posts by Alejandro Duarte