Implementing “Remember Me” in Plain Java With Vaadin 8
Vaadin, together with Java, provide the means for your users to skip logging in, giving you an alternative path for authentication.
Join the DZone community and get the full member experience.
Join For FreeOne 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:
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.
- Log in 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:
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:
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.
Published at DZone with permission of Alejandro Duarte. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments