Apache Wicket: Remember Me functionality

It is quite common in web applications to have “Remember Me” functionality that allows user to be automatically logged in each time when he visits our website.

Such feature can be implemented using Spring Security, but in my opinion using request based authentication framework with component based web framework is not the best idea. These two worlds just do not fit well together so I prefer to use my own baked solution which I will present below.

Base project

We start with a simple web application written using latest, still hot Apache Wicket 6. You can download complete sources from GitHub and start application using mvn clean compile jetty:run .

Base application consists of two pages:

  • Home Page: displays welcome message for logged and not-logged users or either logout or login link.
  • Login Page: allows user to login basing on simple in-memory collection of users. Some working login/password pairs: John/john, Lisa/lisa, Tom/tom .

Remember Me functionality

Standard way to implement Remember Me functionality looks as follows:

  1. Ask user if he wants to be remembered and auto-logged in the future.
  2. If so, save cookies with login and password on his computer.
  3. For every new user coming to our website, check if cookies from step 2 are present and if so, auto login user.
  4. When he manually logs out, remove cookies so it is possible to clear data used to auto-login.

Point 2 needs some explanation. In this example app we are going to save login and not hashed, not encrypted password in cookies. In real scenario this is unacceptable. Instead of it, you should consider storing hashed and salted password so even if someone intercepts user cookie, password still will be secret and more work will be needed to decode it.
Update: Micha? Mat?oka posted two very interesting links how this could be done in real systems. Those approaches do not even use password nor password hashes. For more details please look into his comment below this post.

Step 1: As a User I want to decide if I want to use “Remember Me” feature

Link to commit with this step

To allow user to notify application that he wants to use ‘Remember Me’ functionality we will simply add a checkbox to login page. So we need to amend LoginPage java and html file a bit (new stuff is highlighted):

    <form wicket:id='form' class='form-horizontal'>
        <fieldset>
            <legend>Please login</legend>
        </fieldset>

        <div class='control-group'>
            <div wicket:id='feedback'></div>
        </div>
        <div class='control-group'>
            <label class='control-label' for='login'>Login</label>
            <div class='controls'>
                <input type='text' id='login' wicket:id='login' />
            </div>
        </div>
        <div class='control-group'>
            <label class='control-label' for='password'>Password</label>
            <div class='controls'>
                <input type='password' id='password' wicket:id='password' />
            </div>
        </div>
        <div class='control-group'>
            <div class='controls'>
                <label class='checkbox'>
                    <input type='checkbox' wicket:id='rememberMe'> Remember me on this computer
                </label>
            </div>
        </div>
        <div class='form-actions'>
            <input type='submit' wicket:id='submit' value='Login' title='Login' class='btn btn-primary'/>
        </div>
    </form>
    private String login;
    private String password;
    private boolean rememberMe;

    public LoginPage() {

        Form<Void> loginForm = new Form<Void>('form');
        add(loginForm);

        loginForm.add(new FeedbackPanel('feedback'));
        loginForm.add(new RequiredTextField<String>('login', new PropertyModel<String>(this, 'login')));
        loginForm.add(new PasswordTextField('password', new PropertyModel<String>(this, 'password')));
        loginForm.add(new CheckBox('rememberMe', new PropertyModel<Boolean>(this, 'rememberMe')));

        Button submit = new Button('submit') {
            // (...)
        };

        loginForm.add(submit);
    }

Now we are ready for next step.

Step 2: As a System I want to save login and password in cookies

Link to commit with this step

First we need a CookieService that will encapsulate all logic responsible for working with cookies: saving, listing and clearing cookie when needed. Code is rather simple, we work with WebResponse and WebRequest classes to modify cookies in user’s browser.

public class CookieService {

    public Cookie loadCookie(Request request, String cookieName) {

        List<Cookie> cookies = ((WebRequest) request).getCookies();

        if (cookies == null) {
            return null;
        }

        for (Cookie cookie : cookies) {
            if(cookie.getName().equals(cookieName)) {
                return cookie;
            }
        }

        return null;
    }

    public void saveCookie(Response response, String cookieName, String cookieValue, int expiryTimeInDays) {
        Cookie cookie = new Cookie(cookieName, cookieValue);
        cookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(expiryTimeInDays));
        ((WebResponse)response).addCookie(cookie);
    }

    public void removeCookieIfPresent(Request request, Response response, String cookieName) {
        Cookie cookie = loadCookie(request, cookieName);

        if(cookie != null) {
            ((WebResponse)response).clearCookie(cookie);
        }
    }
}

Then when user checks ‘Remember Me’ on LoginPage, we have to save cookies in his browser:

    Button submit = new Button('submit') {
        @Override
        public void onSubmit() {
            UserService userService = WicketApplication.get().getUserService();

            User user = userService.findByLoginAndPassword(login, password);

            if(user == null) {
                error('Invalid login and/or password. Please try again.');
            }
            else {
                UserSession.get().setUser(user);

                if(rememberMe) {
                    CookieService cookieService = WicketApplication.get().getCookieService();
                    cookieService.saveCookie(getResponse(), REMEMBER_ME_LOGIN_COOKIE, user.getLogin(), REMEMBER_ME_DURATION_IN_DAYS);
                    cookieService.saveCookie(getResponse(), REMEMBER_ME_PASSWORD_COOKIE, user.getPassword(), REMEMBER_ME_DURATION_IN_DAYS);
                }

                setResponsePage(HomePage.class);
            }
        }
    };

Step 3: As a User I want to be auto-logged when I return to web application

Link to commit with this step

To check if user entering our application is a “returning user to auto-login” we have to enrich logic responsible for creating new user session. Currently it is done in WicketApplication class which when requested, creates new WebSession instance. So every time new session is created, we have to check for cookies presence and if they are valid user/password pair, auto-login this user.

So let’s start with extracting session related logic into separate class called SessionProvider. It will need UserService and CookieService to check for existing users and cookies so we pass them as a references in the constructor.

public class WicketApplication extends WebApplication {

    private UserService userService = new UserService();
    private CookieService cookieService = new CookieService();
    private SessionProvider sessionProvider = new SessionProvider(userService, cookieService);

    @Override
    public Session newSession(Request request, Response response) {
        return sessionProvider.createNewSession(request);
    }
}

Role of SessionProvider is to create new UserSession, check if proper cookies are present and if so, set logged user. Additionally we add feedback message to inform user that he was auto logged. So let’s look into the code:

public class SessionProvider {

    public SessionProvider(UserService userService, CookieService cookieService) {
        this.userService = userService;
        this.cookieService = cookieService;
    }

    public WebSession createNewSession(Request request) {
        UserSession session = new UserSession(request);

        Cookie loginCookie = cookieService.loadCookie(request, REMEMBER_ME_LOGIN_COOKIE);
        Cookie passwordCookie = cookieService.loadCookie(request, REMEMBER_ME_PASSWORD_COOKIE);

        if(loginCookie != null && passwordCookie != null) {
            User user = userService.findByLoginAndPassword(loginCookie.getValue(), passwordCookie.getValue());

            if(user != null) {
                session.setUser(user);
                session.info('You were automatically logged in.');
            }
        }

        return session;
    }
}

To show feedback message on HomePage.java we must add FeedbackPanel there, but for the brevity I will omit this in this post. You can read commit to check how to do that.

So we after three steps we should have ‘Remember Me’ working. To check it quickly please modify session timeout in web.xml file by adding:

    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>

and then start application mvn clean compile jetty:run, go to login page, login, close browser and after over a 1 minute (when session expires) open it again on http://localhost:8080. You should see something like this:

So it works. But we still need one more thing: allow user to remove cookies and turn-off auto-login.

Step 4: As a User I want to be able to logout and clear my cookies

Link to commit with this step
In the last step we have to allow user to clear his data and disable “Remember Me” for his account. This will be achieved by clearing both cookies when user explicitly clicks Logout link.

    Link<Void> logoutLink = new Link<Void>('logout') {
        @Override
        public void onClick() {
            CookieService cookieService = WicketApplication.get().getCookieService();
            cookieService.removeCookieIfPresent(getRequest(), getResponse(), SessionProvider.REMEMBER_ME_LOGIN_COOKIE);
            cookieService.removeCookieIfPresent(getRequest(), getResponse(), SessionProvider.REMEMBER_ME_PASSWORD_COOKIE);

            UserSession.get().setUser(null);
            UserSession.get().invalidate();
        }
    };
    logoutLink.setVisible(UserSession.get().userLoggedIn());
    add(logoutLink);


Summary

So that’s all. In this port we have implemented simple ‘Remember Me’ functionality in web application written using Apache Wicket without using any external authentication libraries.

Happy coding and don’t forget to share!

Reference: Remember Me functionality in Apache Wicket from our JCG partner Tomasz Dziurko at the Code Hard Go Pro blog.

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


9 + three =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books