JavaServer Faces Login Form CAPTCHA Code Example

The JSF Login Form Captcha code example shows how to add BotDetect CAPTCHA protection to a typical JavaServer Faces login form.

First Time Here?

Check the BotDetect JSP Captcha Quickstart for key integration steps.

To prevent bots from trying to guess the login info by brute force submission of a large number of common values, the visitor first has to prove they are human (by solving the CAPTCHA), and only then is their username and password submission checked against the authentication data store.

Also, if they enter an invalid username + password combination three times, they have to solve the CAPTCHA again. This prevents attempts in which the attacker would first solve the CAPTCHA themselves, and then let a bot brute-force the authentication info.

To keep the example code simple, the example doesn't access a data store to authenticate the user, but accepts all logins with usernames and passwords at least 5 characters long as valid.

Download the BotDetect Java library and run this example

JavaServer Faces 2.0+

Downloaded Location

The JSF 2.0+ Login Form CAPTCHA code example is included in the
examples/bdc4-jsf20-login-form-captcha-example.war file of the download package. Deploying (unpacking) the file will create a standard JSP directory tree.

Running Example

This example's war file (in BotDetect download package) already embeds all dependencies.

In case you are making this example from scratch in your IDE, you need to ensure botdetect.jar, botdetect-servlet.jar, botdetect-jsp20.jar, and botdetect-jsf20.jar are in the classpath.

index.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@taglib prefix="botDetect" uri="https://captcha.com/java/jsf"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<f:view>
  <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
      <title>BotDetect Java CAPTCHA Validation: JSF Login Form CAPTCHA Code Example</title>
      <link rel="stylesheet" href="stylesheet.css" type="text/css">
    </head>
    <body>
      <h:form prependId="false" styleClass="column">
        <h1>BotDetect Java CAPTCHA Validation:<br/> JSF Login Form CAPTCHA Code Example</h1>
        <fieldset>
          <legend>
            <h:panelGroup rendered="#{!loginForm.isUserLoggedIn}">
              CAPTCHA included in JSF Login form validation
            </h:panelGroup>
            <h:panelGroup rendered="#{loginForm.isUserLoggedIn}">
              Validation passed
            </h:panelGroup>
          </legend>
          
          <h:panelGrid columns="1">
            <h:outputText value="Username:"/>
            <h:inputText id="username" value="#{loginForm.username}"/>

            <h:outputText value="Password:"/>
            <h:inputSecret id="password" value="#{loginForm.password}" rendered="#{!loginForm.isUserLoggedIn}"/>
            <h:inputText value="#{loginForm.password}" rendered="#{loginForm.isUserLoggedIn}"/>

            <h:panelGroup rendered="#{!loginForm.isCaptchaSolved}">
              <h:outputLabel for="captchaCode" value="Retype the characters from the picture:"/>
              <!-- Adding BotDetect Captcha to the page -->
              <botDetect:jsfCaptcha id="exampleCaptcha" 
                        userInputID="captchaCode"
                        imageWidth="200"
                        codeLength="4"
                        codeStyle="ALPHA"
                        binding="#{loginForm.captcha}"/>
              
              <div class="validationDiv">
                <h:inputText id="captchaCode" value="#{loginForm.captchaCode}"/>
              </div>
            </h:panelGroup>
              
            <h:outputText value="#{loginForm.errorMessage}" rendered="#{!loginForm.isUserLoggedIn}" styleClass="incorrect" />
            <h:commandButton action="#{loginForm.login}" value="Login" rendered="#{!loginForm.isUserLoggedIn}"/>
          </h:panelGrid>
              
          <h:outputLink value="#{facesContext.externalContext.requestContextPath}/faces/index.jsp"
                 rendered="#{loginForm.isUserLoggedIn}">Back to login page.</h:outputLink>
        </fieldset>
      </h:form>
    </body>
  </html>
</f:view>

The example login form is simple, containing textboxes for username and password input. We add Captcha protection to the form using the standard procedure.

Note that we check for managed bean isCaptchaSolved property's value to display parts of the form regarding authentication and Captcha validation status.

LoginForm.java

package com.captcha.botdetect.examples.jsf.login_form.view;

import com.captcha.botdetect.web.jsf.JsfCaptcha;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

@ManagedBean(name="loginForm")
@RequestScoped
public class LoginForm {
  
  private JsfCaptcha captcha;
  private String captchaCode;
  private String username, password;
  private String errorMessage;
  private HttpSession session;
  
  private int failedAuthCount;
  private boolean isCaptchaSolved;
  private boolean isUserLoggedIn;

  public LoginForm() {
    FacesContext ctx = FacesContext.getCurrentInstance();
    session = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(true);
    if (ctx.isPostback()) {
      loadSessionAttributes();
    } else {
      saveSessionAttributes();
    }
  }

  public JsfCaptcha getCaptcha() {
    return captcha;
  }

  public void setCaptcha(JsfCaptcha captcha) {
    this.captcha = captcha;
  }

  public String getCaptchaCode() {
    return captchaCode;
  }

  public void setCaptchaCode(String captchaCode) {
    this.captchaCode = captchaCode;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getErrorMessage() {
    return errorMessage;
  }

  public boolean getIsCaptchaSolved() {
    return isCaptchaSolved;
  }
  
  public boolean getIsUserLoggedIn() {
    return isUserLoggedIn;
  }

  public void login() {

    if (validateCaptcha() && validateLogin()) {
      if (authenticate()) {
        errorMessage = "";
        isUserLoggedIn = true;
      } else {
        errorMessage = "Authentication failed";
      
        if (++failedAuthCount > 2) {
          isCaptchaSolved = false;
          failedAuthCount = 0;
        }
      }
    }
    
    captchaCode = "";
    saveSessionAttributes();
  }

  private boolean validateCaptcha() {
    // validate the Captcha to check we're not dealing with a bot
    if (!isCaptchaSolved) {
      if (!this.captcha.validate(captchaCode)) {
        errorMessage = "Invalid code";
        isCaptchaSolved = false;
        return false;
      } else {
        isCaptchaSolved = true;
      }
    }
  
    return true;
  }

  private boolean validateLogin() {
    String pattern = "^[a-zA-Z0-9_]+$"; // alphanumeric chars and underscores only

    boolean validLogin = username.matches(pattern) && password.matches(pattern);
    if (!validLogin) {
      errorMessage = "Invalid authentication info";
    }
    return validLogin;
  }

  private boolean authenticate() {
    return ((username.length() > 4) && (password.length() > 4));
  }

  private void loadSessionAttributes() {
    if (session.getAttribute("failedAuthCount") != null) {
      failedAuthCount = (Integer) session.getAttribute("failedAuthCount");
    }
    
    if (session.getAttribute("isCaptchaSolved") != null) {
      isCaptchaSolved = (Boolean) session.getAttribute("isCaptchaSolved");
    }
  }

  private void saveSessionAttributes() {
    session.setAttribute("failedAuthCount", failedAuthCount);
    session.setAttribute("isCaptchaSolved", isCaptchaSolved);
  }
}

Since we want to reset the Captcha status after 3 failed authentications, we keep the related counter in the failedAuthCount session value.

This allows us to implement the proper Login form workflow:

  • The user must first solve the Captcha to prove they are human. This keeps the bots away from the authentication code, both conserving its resources and improving its security (since usernames and passwords will not be forwarded to the underlying data store if the Captcha is not solved first).
  • When the user has proven they are human, they get 3 authentication attempts without new Captcha tests, which allows them to remember the right combination in most cases.
  • If the user fails three authentication requests, they are shown a new Captcha which they must solve before continuing. This throttles authentication access, ensuring username + password combinations cannot be brute-forced, while real human users get theoretically unlimited authentication attempts (as long as they don't mind solving further Captchas).

As previously mentioned, the actual authentication code is not implemented, but the example code should demonstrate the underlying principles adequately.

After successful authentication, the example just displays the user input (while a real login form would set an auth cookie and redirect the user to actual functionality).

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  id="WebApp_ID" version="2.5">

  <display-name>JavaServerFaces</display-name>

  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>

  <context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.jsp</param-value>
  </context-param>

  <context-param>
    <param-name>facelets.VIEW_MAPPINGS</param-name>
    <param-value>*.xhtml</param-value>
  </context-param>     
 
  <context-param>
    <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name> 
    <param-value>true</param-value>
  </context-param>    

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>BotDetect Captcha</servlet-name>
    <servlet-class>com.captcha.botdetect.web.servlet.CaptchaServlet</servlet-class>
  </servlet>    
  <servlet-mapping>
    <servlet-name>BotDetect Captcha</servlet-name>
    <url-pattern>/botdetectcaptcha</url-pattern>
  </servlet-mapping> 
  
  <welcome-file-list>
    <welcome-file>faces/index.jsp</welcome-file>
  </welcome-file-list>
  
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
</web-app>

In WEB-INF/web.xml file we register CaptchaServlet used for BotDetect Captcha requests.

JavaServer Faces 1.2

The JSF 1.2 Login Form CAPTCHA code example is included in the
examples/bdc4-jsf12-login-form-captcha-example.war file of the download package.


Please Note

BotDetect Java Captcha Library v4.0.Beta1 is an in-progress port of BotDetect 4 Captcha, and we need you to guide our efforts towards a polished product. Please let us know if you encounter any bugs, implementation issues, or a usage scenario you would like to discuss.