In this Java tutorial, you will learn how to add the remember password (Remember Me) feature to your existing Java web application, to allow the users checks the option “Remember Me” in the login form, so they won’t have to enter username/password in future, until they explicitly logout:

Login form with remember me option

Suppose that you develop your website using Java Servlet, JSP and Hibernate framework. If you use other technologies, the design principle and process are the same.

Table of Content:

1. Understand the Solution to Remember Password Feature
2. Create Database Table and Java Domain Model Class
3. Code DAO Class
4. Update Code of the Login Page
5. Update Code of the Login Servlet
6. Update Code of the Authentication Filter
7. Update Code of the Logout Servlet
8. Test the Remember Password Feature

 

1. Understand the Solution to Remember Password Feature

There can be different solutions to implement the remember me functionality, but the security may vary. Here we propose a solution which is proven to be safe and secure. Of course we employ cookie mechanism to store some information in the user’s browser but we don’t store user id, username or password straightforward in cookie – that exposes great security risks.

Instead, we store a pair of (selector, validator) in cookie to authenticate the user.

  • Selector: is a random String generated when the user logs in with the “Remember Me” option checked. A same value is stored in a database table for the purpose of looking up in the process of auto-login for the user.
  • Validator: is a random string associates with the selector. The hashed value is stored in the database. So we compare the hashed value of the validator in the cookie against the value in the database to perform auto-login.
The following picture illustrates the concept of selector-validator:

selector validator concept

As you can see, this solution leads to the need of creating an additional table in the database to store pairs of selector-validator. And this table has a foreign key references to the user table in order to look up the associated user. This solution has the following advantages:

  • No sensitive information (user id, username and password) is stored in the cookie.
  • The random selector eliminates brute-force attacks in which the hackers attempt to try many user ids or usernames.
  • The value of validator in the database is hashed using a crypto algorithm like SHA-256. If somehow the authentication table is leak, the hacker cannot generate fake cookies.
  • Allow remember password for a user on different browsers and different computers independently.
And the following activity diagram describes the workflow to implement the remember me function when the user/customer logs in:



login with remember me workflow

In case an authentication filter is used, we need to update the workflow as described in the following diagram:

remember me workflow

Next, let’s go through the coding steps to implement the Remember Me feature for your website. Note that in the following code, we use the table customer instead of user.


2. Create Database Table and Java Domain Model Class

As per the solution we discussed above, we need to create an authentication table that looks like this:

remember me database design

Here, customer is an existing table and customer_auth is the additional table added to implement the remember me function.

Suppose that we use Hibernate/JPA, so create a new domain model class CustomerAuthToken to map with the customer_auth table.

// copyright www.codejava.net

import javax.persistence.*;

@Entity
@Table(name = "customer_auth")
@NamedQueries({
	@NamedQuery(name = "CustomerAuthToken.findBySelector", 
			query = "SELECT c FROM CustomerAuthToken c WHERE c.selector = :selector")
})
public class CustomerAuthToken implements java.io.Serializable {
	private Long id;
	private String selector;
	private String validator;
	private Customer customer;

	public CustomerAuthToken() {
	}


	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public Long getId() {
		return id;
	}

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "customer_id", nullable = false)	
	public Customer getCustomer() {
		return customer;
	}
	
	// other getters and setters are hidden for brevity

}
Note that the customer table has one-to-many relationship with the customer_auth table, so in we also need to update code of the Customer domain model class as follows:

// copyright www.codejava.net

import javax.persistence.*;

@Entity
public class Customer implements java.io.Serializable {

	private Set<CustomerAuthToken> customerAuthTokens = new HashSet<>(0);
	
	// other fields


	@OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")	
	public Set<CustomerAuthToken> getCustomerAuthTokens() {
		return customerAuthTokens;
	}

	// other setters and getters...	
}
This allows us to look up a customer associated with a token (selector, validator) like this:

CustomerAuthToken token = …
Customer customer = token.getCustomer();
We refer a pair of (selector, validator) as a token.


3. Code DAO Class

Next, code the CustomerAuthDAO class just like a typical Hibernate/JPA DAO class with an additional method that finds a CustomerAuthToken by selector:

// copyright www.codejava.net

public class CustomerAuthDAO extends JpaDAO<CustomerAuthToken> 
		implements GenericDAO<CustomerAuthToken> {

	public CustomerAuthToken findBySelector(String selector) {
		List<CustomerAuthToken> list = super.findWithNamedQuery(
			"CustomerAuthToken.findBySelector", "selector", selector);
			
		if (!list.isEmpty()) {
			return list.get(0);
		}
		
		return null;
	}
	
	
}
The findBySelector() method will be used to find a token in the authentication table, given a selector read from the cookie.

Learn more about JPA/Hibernate here: Java Hibernate JPA Annotations Tutorial for Beginners


4. Update Code of the Login Page

In the login JSP page, add a checkbox to present the “Remember Me” option:

<input type="checkbox" name="rememberMe" value="true">Remember Me
The login form should look like this:

Login form with remember me option


5. Update Code of the Login Servlet

Suppose that you have a Java Servlet class to handle the login submission request. So we need to read the value of the remember option, and if the user/customer has logged in successfully, create a new token and save it into the database, and then create cookies to store the pair of selector-validator.

The workflow of the code should look like this:

// copyright www.codejava.net

public void doLogin() throws ServletException, IOException {
	String email = request.getParameter("email");
	String password = request.getParameter("password");
	
	boolean rememberMe = "true".equals(request.getParameter("rememberMe"));
	
	Customer customer = customerDAO.checkLogin(email, password);
	
	if (customer == null) {
	
		// login failed, show login form again with error messsage
		
	} else {
		// login succeed, store customer information in the session
		HttpSession session = request.getSession();
		session.setAttribute("loggedCustomer", customer);
	
	
		if (rememberMe) {
			// create new token (selector, validator)
			
			// save the token into the database
			
			// create a cookie to store the selector
			
			// create a cookie to store the validator
				
		}
		
		// show destination page
	}
}
Here’s the code to create a new token:

// copyright www.codejava.net

CustomerAuthToken newToken = new CustomerAuthToken();

String selector = RandomStringUtils.randomAlphanumeric(12);
String rawValidator =  RandomStringUtils.randomAlphanumeric(64);

String hashedValidator = HashGenerator.generateSHA256(rawValidator);

newToken.setSelector(selector);
newToken.setValidator(hashedValidator);

newToken.setCustomer(customer);
We use the RandomStringUtils class from Apache Commons Lang library, so make sure you add the following dependency to your project:

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.8.1</version>
</dependency>
And HashGeneratoris a utility class. Read this tutorial to create it: How to calculate MD5 and SHA hash values in Java

Notice we set a Customer object for the token:

newToken.setCustomer(customer);
This Customer object is the result of the check login process:

Customer customer = customerDAO.checkLogin(email, password);
And save the new token into the database:

CustomerAuthDAO authDao =  new CustomerAuthDAO();
authDao.create(newToken);
And here’s the code to create two cookies to store the values of selector and validator:

Cookie cookieSelector = new Cookie("selector", selector);
cookieSelector.setMaxAge(604800);

Cookie cookieValidator = new Cookie("validator", rawValidator);
cookieValidator.setMaxAge(604800);

response.addCookie(cookieSelector);
response.addCookie(cookieValidator);
Note that we set the max age of the cookies to 604,800 seconds = 7 days so they are stored permanently in the browser for that time, even when the user exits the browser. You can change this value depending on your need.


6. Update Code of the Authentication Filter

Typically, the workflow of an authentication filter looks like this:

// copyright www.codejava.net

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	
	// check user's login status in the session
	
	
	if (user is not logged in && current page requires authentication) {
		
		// forward to the login page
		
		
	} else {
		// user is logged in, move forward
		chain.doFilter(request, response);
	}
}
To implement the remember password feature, the workflow should be updated like this:

// copyright www.codejava.net

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	
	// check user's login status in the session

	// read cookie
	
	if (user is not logged in && has cookie) {
		// read (selector, validator) from cookies
		
		// find a token by the selector in the database
		
		if (token is found) {
			// read the hashed value of validator from the token -> v1
			// hash the validator from the database -> v2
			
			if (v1 equals v2) {
				// user is authenticated, set login status in session
				
				// update new token in the database
				
				// update cookies
			}
		}
	}
	
	if (user is not logged in && current page requires authentication) {
		
		// forward to the login page
		
		
	} else {
		// user is logged in, move forward
		chain.doFilter(request, response);
	}
}
And here’s the example code to implement remember password feature in the authentication filter:

// copyright www.codejava.net

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);

boolean loggedIn = session != null && session.getAttribute("loggedCustomer") != null;

Cookie[] cookies = httpRequest.getCookies();

if (!loggedIn && cookies != null) {
	// process auto login for remember me feature
	String selector = "";
	String rawValidator = "";	
	
	for (Cookie aCookie : cookies) {
		if (aCookie.getName().equals("selector")) {
			selector = aCookie.getValue();
		} else if (aCookie.getName().equals("validator")) {
			rawValidator = aCookie.getValue();
		}
	}
	
	if (!"".equals(selector) && !"".equals(rawValidator)) {
		CustomerAuthDAO authDAO = new CustomerAuthDAO();
		CustomerAuthToken token = authDAO.findBySelector(selector);
		
		if (token != null) {
			String hashedValidatorDatabase = token.getValidator();
			String hashedValidatorCookie = HashGenerator.generateSHA256(rawValidator);
			
			if (hashedValidatorCookie.equals(hashedValidatorDatabase)) {
				session = httpRequest.getSession();
				session.setAttribute("loggedCustomer", token.getCustomer());
				loggedIn = true;
				
				// update new token in database
				String newSelector = RandomStringUtils.randomAlphanumeric(12);
				String newRawValidator =  RandomStringUtils.randomAlphanumeric(64);
				
				String newHashedValidator = HashGenerator.generateSHA256(newRawValidator);
				
				token.setSelector(newSelector);
				token.setValidator(newHashedValidator);
				authDAO.update(token);
				
				// update cookie
				Cookie cookieSelector = new Cookie("selector", newSelector);
				cookieSelector.setMaxAge(604800);
				
				Cookie cookieValidator = new Cookie("validator", newRawValidator);
				cookieValidator.setMaxAge(604800);
				
				httpResponse.addCookie(cookieSelector);
				httpResponse.addCookie(cookieValidator);						
				
			}
		}
	}
	
}
Note that after the user has been automatically authenticated, we create new values for selector and validator in order to tighten security.

Learn more about authentication filter: How to implement authentication filter for Java web application


7. Update Code of the Logout Servlet

Finally, if the user explicitly logs out from the website, we need to delete the cookies and token from the database. Here’s the example code in a Java servlet that handles logout request:

// copyright www.codejava.net

protected void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	request.getSession().removeAttribute("loggedCustomer");
	
	Cookie[] cookies = request.getCookies();
	
	if (cookies != null) {
		String selector = "";
		
		for (Cookie aCookie : cookies) {
			if (aCookie.getName().equals("selector")) {
				selector = aCookie.getValue();
			}
		}
		
		if (!selector.isEmpty()) {
			// delete token from database
			CustomerAuthDAO authDao = new CustomerAuthDAO();
			CustomerAuthToken token = authDao.findBySelector(selector);
			
			if (token != null) {
				authDao.delete(token.getId());
				
				Cookie cookieSelector = new Cookie("selector", "");
				cookieSelector.setMaxAge(0);
				
				Cookie cookieValidator = new Cookie("validator", "");
				cookieValidator.setMaxAge(0);
				response.addCookie(cookieSelector);
				response.addCookie(cookieValidator);					
			}
		}
	}
	
	
	response.sendRedirect(request.getContextPath());
}
Note that in the above code, we set the max age of the cookies to 0 to remove them.


8. Test the Remember Password Feature

When testing, check the data inserted into the authentication table:

remember password data in authentication table

And also check cookies in the browser:

remember me cookie in browser

If you test the website on localhost with Chrome, type the following URL to see the cookies:

chrome://settings/cookies/detail?site=localhost

Remember to test with different users, different browsers and different computers.

That’s my step-by-step guide to implement the remember me/remember password feature for an existing Java web application. For learning to develop full website, I recommend you to take the Java Servlet, JSP and Hibernate: Build a Complete Website course.

 

Other Java Coding Tutorials:


About the Author:

is certified Java programmer (SCJP and SCWCD). He started programming with Java in the time of Java 1.4 and has been falling in love with Java since then. Make friend with him on Facebook and watch his Java videos you YouTube.



Add comment

   


Comments 

#7Nam2021-11-16 20:33
Yes, Zied.
I choose Set which does not allow duplicate elements.
Quote
#6Zied2021-11-15 16:29
Great tutorial! I was wondering why did you choose the Set class instead of List? is uniqueness an important feature?
Quote
#5Thuy2021-02-04 11:38
Hi,
I wonder what happens if there are two same selectors in the database (it is possible because the selector is generated randomly), in the method .findbyselector, you take the first one, but it is not true if the current user is associated with the second selector. I don't see the unique key of attribute selector in the table customer_auth. Many thanks to your tutorials.
Quote
#4Nam2020-05-25 06:42
hi Ilya,
Simply delete all rows from that able. Then all remembered users are required to login again.
Quote
#3Nam2020-05-25 06:39
Hi Đỗ Nguyên Thạnh,
No solution to fix. If that case happens, meaning that you have permission on that computer, or you are able to hack the browser.
If I have 2 keys and you get one, you are able to open my house's door just like me.
Quote