15.3. Authentication

Seam Security provides Java Authentication and Authorization Service (JAAS) based authorization features, providing a robust and highly configurable API for handling user authentication. If your authentication needs are not this complex, Seam also offers a simplified authentication method.

15.3.1. Configuring an Authenticator component

Note

If you use Seam's Identity Management features, you can skip this section — it is not necessary to create an authenticator component.
Seam's simplified authentication method uses a built-in JAAS login module (SeamLoginModule) to delegate authentication to one of your own Seam components. (This module requires no additional configuration files, and comes pre-configured within Seam.) With this, you can write an authentication method with the entity classes provided by your own application, or authenticate through another third-party provider. Configuring this simplified authentication requires the identity component to be configured in components.xml

             <components xmlns="http://jboss.org/schema/seam/components" 
xmlns:core="http://jboss.org/schema/seam/core" 
xmlns:security="http://jboss.org/schema/seam/security" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation= 
   "http://jboss.org/schema/seam/components 
   http://jboss.org/schema/seam/components-2.3.xsd 
   http://jboss.org/schema/seam/security 
    http://jboss.org/schema/seam/security-2.3.xsd">

<security:identity authenticate-method="#{authenticator.authenticate}"/>

</components>
#{authenticator.authenticate} is a method binding that indicates the authenticate method of the authenticator component will be used to authenticate the user.

15.3.2. Writing an authentication method

The authenticate-method property specified for identity in components.xml specifies the method used by SeamLoginModule to authenticate users. This method takes no parameters, and is expected to return a Boolean indicating authentication success or failure. Username and password are obtained from Credentials.getUsername() and Credentials.getPassword() respectively. (A reference to the credentials component can be obtained via Identity.instance().getCredentials().) Any role that the user is a member of should be assigned with Identity.addRole(). The following is a complete example of an authentication method inside a POJO component:
@Name("authenticator")
public class Authenticator {
  @In EntityManager entityManager;
  @In Credentials credentials;
  @In Identity identity;

  public boolean authenticate() {
    try {
      User user = (User) entityManager.createQuery(
          "from User where username = :username and password = :password")
          .setParameter("username", credentials.getUsername())
          .setParameter("password", credentials.getPassword())
          .getSingleResult();

      if (user.getRoles() != null) {
        for (UserRole mr : user.getRoles())
          identity.addRole(mr.getName());
        }

        return true;
      } catch (NoResultException ex) {
        return false;
      }

   }

}
In the example, both User and UserRole are application-specific entity beans. The roles parameter is populated with roles that the user is a member of. This is added to the Set as literal string values — for example, "admin", "user", etc. If the user record is not found, and a NoResultException is thrown, the authentication method returns false to indicate authentication failure.

Note

It is important to keep authenticator methods minimal and free from any side-effects — they can be invoked multiple times during a single request, so any special code that should execute when authentication succeeds or fails should implement an event observer. See Section 15.10, “Security Events” later in this chapter for more information about events raised by Seam Security.

15.3.2.1. Identity.addRole()

The Identity.addRole() method's behavior depends upon current session authentication. If the session is not authenticated, addRole() should only be called during the authentication process. When called here, the role name is placed in a temporary list of pre-authenticated roles. Once authentication succeeds, the pre-authenticated roles then become "real" roles, and calling Identity.hasRole() for those roles returns true. The following sequence diagram represents the list of pre-authenticated roles as a first class object to clarify its position in the authentication process.
If the current session is already authenticated, then calling Identity.addRole() grants the specified role to the current user immediately.

15.3.2.2. Writing an event observer for security-related events

If, upon successful log in, some user statistics require updates, you can write an event observer for the org.jboss.seam.security.loginSuccessful event, like this:
   
@In UserStats userStats;

@Observer("org.jboss.seam.security.loginSuccessful")
public void updateUserStats() {
  userStats.setLastLoginDate(new Date());
  userStats.incrementLoginCount();
}
This observer method can be placed anywhere, even in the Authenticator component itself. More information about other security-related events appears later in the chapter.

15.3.3. Writing a login form

The credentials component provides both username and password properties, catering for the most common authentication scenario. These properties can be bound directly to the username and password fields on a login form. Once these properties are set, calling identity.login() authenticates the user with the credentials provided. An example of a simple login form is as follows:
<div>
  <h:outputLabel for="name" value="Username"/>
  <h:inputText id="name" value="#{credentials.username}"/>
</div>

<div>
  <h:outputLabel for="password" value="Password"/>
  <h:inputSecret id="password" value="#{credentials.password}"/>
</div>

<div>
  <h:commandButton value="Login" action="#{identity.login}"/>
</div>
Similarly, the user is logged out by calling #{identity.logout}. This action clears the security state of the currently authenticated user and invalidate the user's session.

15.3.4. Configuration Summary

There are three easy steps to configure authentication:
  • Configure an authentication method in components.xml.
  • Write an authentication method.
  • Write a login form so that the user can authenticate.

15.3.5. Remember Me

Seam Security supports two different modes of the Remember Me functionality common to many web-based applications. The first mode allows the username to be stored in the user's browser as a cookie, and leaves the browser to remember the password. The second mode stores a unique token in a cookie, and lets a user authenticate automatically when they return to the site, without having to provide a password.

Warning

Although it is convenient for users, automatic client authentication through a persistent cookie on the client machine is dangerous because the effects of any cross-site scripting (XSS) security hole are magnified. Without the authentication cookie, the only cookie an attacker can steal with XSS is the user's current session cookie — so an attack can only occur while a user has a session open. If a persistent Remember Me cookie is stolen, an attacker can log in without authentication at any time. If you wish to use automatic client authentication, it is vital to protect your website against XSS attacks.
Browser vendors introduced the Remember Passwords feature to combat this issue. Here, the browser remembers the username and password used to log in to a particular website and domain, and automatically fills in the login form when there is no session active. A log in keyboard shortcut on your website can make the log in process almost as convenient as the "Remember Me" cookie, and much safer. Some browsers (for example, Safari on OS X) store the login form data in the encrypted global operation system keychain. In a networked environment, the keychain can be transported with the user between laptop and desktop — cookies are not usually synchronized.
Although persistent Remember Me cookies with automatic authentication are widely used, they are bad security practice. Cookies that recall only the user's login name, and fill out the login form with that username as a convenience, are much more secure.
No special configuration is required to enable the Remember Me feature for the default (safe, username-only) mode. In your login form, simply bind the Remember Me checkbox to rememberMe.enabled, as seen in the following example:
<div>
  <h:outputLabel for="name" value="User name"/>
  <h:inputText id="name" value="#{credentials.username}"/>
</div>
  
<div>
  <h:outputLabel for="password" value="Password"/>
  <h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
</div>      
  
<div class="loginRow">
  <h:outputLabel for="rememberMe" value="Remember me"/>
  <h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>
</div>

15.3.5.1. Token-based Remember Me Authentication

To use the automatic, token-based mode of the Remember Me feature, you must first configure a token store. These authentication tokens are commonly stored within a database. Seam supports this method, but you can also implement your own token store by using the org.jboss.seam.security.TokenStore interface. This section assumes that you will be using the provided JpaTokenStore implementation to store authentication tokens inside a database table.
First, create a new Entity to hold the tokens. The following is one possible structure:
@Entity
public class AuthenticationToken implements Serializable {  
  private Integer tokenId;
  private String username;
  private String value;
   
  @Id @GeneratedValue
  public Integer getTokenId() {
    return tokenId;
  }
   
  public void setTokenId(Integer tokenId) {
     this.tokenId = tokenId;
  }
   
  @TokenUsername
  public String getUsername() {
     return username;
  }
   
  public void setUsername(String username) {
    this.username = username;
  }
   
  @TokenValue
  public String getValue() {
    return value;
  }
   
  public void setValue(String value) {
    this.value = value;
  }
}
Several special annotations, @TokenUsername and @TokenValue, are used to configure the username and token properties of the entity. These annotations are required for the entity that holds the authentication tokens.
The next step is to configure JpaTokenStore to store and retrieve authentication tokens with this entity bean. Do this by specifying the token-class attribute in components.xml:
 
<security:jpa-token-store 
     token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>
The final step is to configure the RememberMe component in components.xml. Its mode should be set to autoLogin:
  
<security:remember-me mode="autoLogin"/>
Users who check the Remember Me checkbox will now be authenticated automatically.
To ensure that users are automatically authenticated when returning to the site, the following section should be placed in components.xml:
<event type="org.jboss.seam.security.notLoggedIn">
  <action execute="#{redirect.captureCurrentView}"/>
  <action execute="#{identity.tryLogin()}"/>
</event>
<event type="org.jboss.seam.security.loginSuccessful">
  <action execute="#{redirect.returnToCapturedView}"/>
</event>

15.3.6. Handling Security Exceptions

So that users do not receive a basic default error page when a security error occurs, you should edit pages.xml to redirect users to a more attractive page. The two main exceptions thrown by the security API are:
  • NotLoggedInException — This exception is thrown when the user attempts to access a restricted action or page when they are not logged in.
  • AuthorizationException — This exception is only thrown if the user is already logged in, and they have attempted to access a restricted action or page for which they do not have the necessary privileges.
In the case of a NotLoggedInException, we recommend the user be redirected to a login or registration page so that they can log in. For an AuthorizationException, it may be useful to redirect the user to an error page. Here's an example of a pages.xml file that redirects both of these security exceptions:
<pages>

  ...

  <exception class="org.jboss.seam.security.NotLoggedInException">
    <redirect view-id="/login.xhtml">
      <message>You must be logged in to perform this action</message>
    </redirect>
  </exception>

  <exception class="org.jboss.seam.security.AuthorizationException">
    <end-conversation/>
      <redirect view-id="/security_error.xhtml">
        <message>
          You do not have the necessary security privileges to perform this action.
        </message>
      </redirect>
  </exception>

</pages>
Most web applications require more sophisticated handling of login redirection. Seam includes some special functionality, outlined in the following section.

15.3.7. Login Redirection

When an unauthenticated user tries to access a particular view or wildcarded view ID, you can have Seam redirect the user to a login screen as follows:
<pages login-view-id="/login.xhtml">

  <page view-id="/members/*" login-required="true"/> 
... 
</pages>

Note

This is more refined than the exception handler shown above, but should probably be used in conjunction with it.
After the user logs in, we want to automatically redirect them to the action that required log in. If you add the following event listeners to components.xml, attempts to access a restricted view while not logged in are remembered. Upon a successful log in, the user is redirected to the originally requested view, with any page parameters that existed in the original request.
<event type="org.jboss.seam.security.notLoggedIn">
  <action execute="#{redirect.captureCurrentView}"/>
</event>

<event type="org.jboss.seam.security.postAuthenticate">
  <action execute="#{redirect.returnToCapturedView}"/>
</event>

Note

Login redirection is implemented as a conversation-scoped mechanism, so do not end the conversation in your authenticate() method.

15.3.8. HTTP Authentication

Although we do not recommend it unless absolutely necessary, Seam provides the means to authenticate with either HTTP Basic or HTTP Digest (RFC 2617) methods. For either form, you must first enable the authentication-filter component in components.xml:
<web:authentication-filter url-pattern="*.seam" auth-type="basic"/> 
To enable basic authentication, set auth-type to basic. For digest authentication, set it to digest. If you want to use digest authentication, you must also set the key and realm:
<web:authentication-filter url-pattern="*.seam" auth-type="digest" 
     key="AA3JK34aSDlkj" realm="My App"/> 
The key can be any String value. The realm is the name of the authentication realm that is presented to the user when they authenticate.

15.3.8.1. Writing a Digest Authenticator

If using digest authentication, your authenticator class should extend the abstract class org.jboss.seam.security.digest.DigestAuthenticator, and use the validatePassword() method to validate the user's plain text password against the digest request. Here is an example:
public boolean authenticate() {
  try {
    User user = (User) entityManager.createQuery(
         "from User where username = "username")
         .setParameter("username", identity.getUsername())
         .getSingleResult();

    return validatePassword(user.getPassword());
  } catch (NoResultException ex) {
    return false;
  }
}

15.3.9. Advanced Authentication Features

This section explores some of the advanced features provided by the security API for addressing more complex security requirements.

15.3.9.1. Using your container's JAAS configuration

If you prefer not to use the simplified JAAS configuration provided by the Seam Security API, you can use the default system JAAS configuration by adding a jaas-config-name property to components.xml. For example, if you use Red Hat JBoss Enterprise Application Platform and want to use the other policy (which uses the UsersRolesLoginModule login module provided by JBoss Enterprise Application Platform), then the entry in components.xml would look like this:
<security:identity jaas-config-name="other"/>
Keep in mind that doing this does not mean that your user will be authenticated in your Seam application container — it instructs Seam Security to authenticate itself with the configured JAAS security policy.