#HOWTO: Simple form-based authentication for JSF 2.3 with Java EE 8 Security API

Securing your web application can be cumbersome. I recently tried to secure a JSF 2.3 application with the latest Java EE 8 Security API (JSR-375) and it was quite simple. In this blog post, I’ll show you the required configuration steps for securing your JSF application with a form-based authentication mechanism. In the example, I’ll use an in-memory user store of two users, add PrimeFaces for enhancing the UI and deploy it to Payara 5.182.

The pom.xml is straightforward for this application:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>de.rieckpil.blog</groupId>
    <artifactId>jsf-simple-login-with-java-ee-security-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <repositories>
        <repository>
            <id>prime-repo</id>
            <name>Prime Repo</name>
            <url>http://repository.primefaces.org</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
        </dependency>
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>6.2</version>
        </dependency>
        <dependency>
            <groupId>org.primefaces.themes</groupId>
            <artifactId>all-themes</artifactId>
            <version>1.0.10</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>jsf-simple-login</finalName>
    </build>
</project>

The new Security API is part of Java EE 8 and the definitions are mostly located in the javax.security package. The default reference implementation is Soteria and is already part of the most recent application servers (Payara, Wildfly, OpenLiberty, TomEE …). With this new API, we now get a common security workflow definition and with the new interfaces and annotations, we can configure the authentication and authorization of our Java EE applications.  One of the central interfaces is the IdentityStore which is responsible for validating the incoming credentials. In this example, I’m using a simple in-memory IdentityStore which has only two valid users:

@ApplicationScoped
public class CustomInMemoryIdentityStore implements IdentityStore {

    @Override
    public CredentialValidationResult validate(Credential credential) {

        UsernamePasswordCredential login = (UsernamePasswordCredential) credential;

        if (login.getCaller().equals("admin@mail.com") 
                       && login.getPasswordAsString().equals("ADMIN1234")) {
            return new CredentialValidationResult("admin", new HashSet<>(Arrays.asList("ADMIN")));
        } else if (login.getCaller().equals("user@mail.com") 
                       && login.getPasswordAsString().equals("USER1234")) {
            return new CredentialValidationResult("user", new HashSet<>(Arrays.asList("USER")));
        } else {
            return CredentialValidationResult.NOT_VALIDATED_RESULT;
        }
    }
}

For a more realistic example you can use the new @DatabaseIdentityStoreDefinition or @LdapIdentityStoreDefinition annotation to configure a database or a LDAP system for user validation. Our IdentityStore will just check if the incoming mail and password match with one of the two dummy users and return a CredentialValidationResult which contains the principal’s name and the associated roles.

The web application has two pages: the login.xhtml file at the root level which is visible for everybody and a secured page at /app/index.html. To secure a path in your JSF application, you have to update your web.xml. In our example, I am securing the path to /app/* to be visible only for authenticated users with the ADMIN or USER role:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">


    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Application pages</web-resource-name>
            <url-pattern>/app/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

    <security-role>
        <role-name>USER</role-name>
    </security-role>
    <security-role>
        <role-name>ADMIN</role-name>
    </security-role>

    <context-param>
        <param-name>primefaces.THEME</param-name>
        <param-value>bootstrap</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>*.xhtml</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>app/index.xhtml</welcome-file>
    </welcome-file-list>

</web-app>

To register a custom login page, you have to inform the authentication mechanism about the name and location of your login page. This can be done programmatically with a simple configuration class:

@CustomFormAuthenticationMechanismDefinition(
        loginToContinue = @LoginToContinue(
                loginPage = "/login.xhtml",
                useForwardToLogin = false
            )
)
@FacesConfig
@ApplicationScoped
public class ApplicationConfig {
}

The property useForwardToLogin is set to false to use a redirect instead of a forward.

The login.xhtml file is a simple JSF page containing a form with two input fields:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:p="http://primefaces.org/ui">
<f:view>
    <h:head>
        <h:outputStylesheet>
            h3 {
                text-align: center;
            }
        </h:outputStylesheet>
    </h:head>
    <h:body>
        <p:outputPanel>
            <div class="ui-g">
                <div class="ui-g-4"/>
                <div class="ui-g-4">
                    <h3>Login</h3>

                    <h:form id="login">
                        <p:messages id="messages" globalOnly="true"/>

                        <h:panelGrid columns="3" cellpadding="5" style="margin: 0 auto;">

                            <p:outputLabel for="email" value="E-Mail"/>
                            <p:inputText id="email" value="#{loginBacking.email}"/>
                            <p:message for="email"/>

                            <p:outputLabel for="password" value="Password"/>
                            <p:inputText type="password" id="password" value="#{loginBacking.password}"/>
                            <p:message for="password"/>

                            <p:commandButton id="submit" update="@form" 
                                 value="Login" action="#{loginBacking.submit}"/>
                        </h:panelGrid>
                    </h:form>
                </div>
            </div>
        </p:outputPanel>
    </h:body>
</f:view>
</html>

The backing bean for the login page is responsible for validating the incoming credentials and redirects to the secured area if the credentials are valid. To use our custom IdentityStore the backing beans injects the SecurityContext and passes the email and password to our in-memory authentication. With the bean validation annotations we get a pre-validation (e.g. check the password against custom password rules) of the user input out of the box:

@Named
@RequestScoped
public class LoginBacking {

    @NotEmpty
    @Size(min = 8, message = "Password must have at least 8 characters")
    private String password;

    @NotEmpty
    @Email(message = "Please provide a valid e-mail")
    private String email;

    @Inject
    private SecurityContext securityContext;

    @Inject
    private ExternalContext externalContext;

    @Inject
    private FacesContext facesContext;

    public void submit() throws IOException {

        switch (continueAuthentication()) {
            case SEND_CONTINUE:
                facesContext.responseComplete();
                break;
            case SEND_FAILURE:
                facesContext.addMessage(null,
                        new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login failed", null));
                break;
            case SUCCESS:
                facesContext.addMessage(null,
                        new FacesMessage(FacesMessage.SEVERITY_INFO, "Login succeed", null));
                externalContext.redirect(externalContext.getRequestContextPath() + "/app/index.xhtml");
                break;
            case NOT_DONE:
        }
    }

    private AuthenticationStatus continueAuthentication() {
        return securityContext.authenticate(
                (HttpServletRequest) externalContext.getRequest(),
                (HttpServletResponse) externalContext.getResponse(),
                AuthenticationParameters.withParams()
                  .credential(new UsernamePasswordCredential(email, password))
        );
    }

   // getters & setters
}

Entering valid credentials will redirect the user to the secured index.html which will display the user’s name and a button to logout:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:p="http://primefaces.org/ui">
<f:view>
    <h:head></h:head>
    <h:body>
        <p:outputLabel>Welcome to the application!</p:outputLabel>

        <p>Logged-in as #{request.userPrincipal.name}</p>

        <h:form>
            <p:commandButton value="Logout" action="#{logoutBacking.submit}"/>
        </h:form>
    </h:body>
</f:view>
</html>

The backing bean for the logout is invalidating the session of the user and will redirect to the login page:

@Named
@RequestScoped
public class LogoutBacking {

    public String submit() {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        return "/login.xhtml?faces-redirect=true";
    }
}

To try this on your machine, have a look at my GitHub repository for the full code base and a step-by-step tutorial to get this running on your machine in the README.md of the project.

There is quite more to discover with JSF 2.3 and Java EE in general, so stay tuned for the next blog posts. To start with JSF 2.3 I can recommend The Definitive Guide to JSF in Java EE 8 from Bauke Scholtz (@OmniFaces) & Arjan Tijms (@arjan_tijms).

Have fun securing your applications,

Phil


 

 

2 Comments

  1. Cassius Vinicius September 7, 2018 at 10:11 pm

    Hello,

    I am using Wildfly 14 which is Java EE 8 compliant.

    This project is not working, appears “Forbidden” instead of the login page.

    Any clues, please ?

    1. rieckpil September 8, 2018 at 7:36 am

      Hey Cassius,

      could you provide me a link to a Github repository, so that I can have a look at it?

      Kind regards,
      Phil

Leave a comment

Your email address will not be published. Required fields are marked *