Seam FrameworkCommunity Documentation

Chapter 16. Security

16.1. Overview
16.2. Disabling Security
16.3. Authentication
16.3.1. Configuring an Authenticator component
16.3.2. Writing an authentication method
16.3.3. Writing a login form
16.3.4. Configuration Summary
16.3.5. Remember Me
16.3.6. Handling Security Exceptions
16.3.7. Login Redirection
16.3.8. HTTP Authentication
16.3.9. Advanced Authentication Features
16.4. Identity Management
16.4.1. Configuring IdentityManager
16.4.2. JpaIdentityStore
16.4.3. LdapIdentityStore
16.4.4. Writing your own IdentityStore
16.4.5. Authentication with Identity Management
16.4.6. Using IdentityManager
16.5. Error Messages
16.6. Authorization
16.6.1. Core concepts
16.6.2. Securing components
16.6.3. Security in the user interface
16.6.4. Securing pages
16.6.5. Securing Entities
16.6.6. Typesafe Permission Annotations
16.6.7. Typesafe Role Annotations
16.6.8. The Permission Authorization Model
16.6.9. RuleBasedPermissionResolver
16.6.10. PersistentPermissionResolver
16.7. Permission Management
16.7.1. PermissionManager
16.7.2. Permission checks for PermissionManager operations
16.8. SSL Security
16.8.1. Overriding the default ports
16.9. CAPTCHA
16.9.1. Configuring the CAPTCHA Servlet
16.9.2. Adding a CAPTCHA to a form
16.9.3. Customising the CAPTCHA algorithm
16.10. Security Events
16.11. Run As
16.12. Extending the Identity component
16.13. OpenID
16.13.1. Configuring OpenID
16.13.2. Presenting an OpenIdDLogin form
16.13.3. Logging in immediately
16.13.4. Deferring login
16.13.5. Logging out

The Seam Security API provides a multitude of security-related features for your Seam-based application, covering such areas as:

This chapter will cover each of these features in detail.

In some situations it may be necessary to disable Seam Security, for instances during unit tests or because you are using a different approach to security, such as native JAAS. Simply call the static method Identity.setSecurityEnabled(false) to disable the security infrastructure. Of course, it's not very convenient to have to call a static method when you want to configure the application, so as an alternative you can control this setting in components.xml:

Assuming you are planning to take advantage of what Seam Security has to offer, the rest of this chapter documents the plethora of options you have for giving your user an identity in the eyes of the security model (authentication) and locking down the application by establishing constraints (authorization). Let's begin with the task of authentication since that's the foundation of any security model.

The authentication features provided by Seam Security are built upon JAAS (Java Authentication and Authorization Service), and as such provide a robust and highly configurable API for handling user authentication. However, for less complex authentication requirements Seam offers a much more simplified method of authentication that hides the complexity of JAAS.

The simplified authentication method provided by Seam uses a built-in JAAS login module, SeamLoginModule, which delegates authentication to one of your own Seam components. This login module is already configured inside Seam as part of a default application policy and as such does not require any additional configuration files. It allows you to write an authentication method using the entity classes that are provided by your own application, or alternatively to authenticate with some other third party provider. Configuring this simplified form of 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>

The EL expression #{authenticator.authenticate} is a method binding that indicates the authenticate method of the authenticator component will be used to authenticate the user.

The authenticate-method property specified for identity in components.xml specifies which method will be used by SeamLoginModule to authenticate users. This method takes no parameters, and is expected to return a boolean, which indicates whether authentication is successful or not. The user's username and password can be obtained from Credentials.getUsername() and Credentials.getPassword(), respectively (you can get a reference to the credentials component via Identity.instance().getCredentials()). Any roles that the user is a member of should be assigned using Identity.addRole(). Here's 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 above example, both User and UserRole are application-specific entity beans. The roles parameter is populated with the roles that the user is a member of, which should be added to the Set as literal string values, e.g. "admin", "user". In this case, if the user record is not found and a NoResultException thrown, the authentication method returns false to indicate the authentication failed.

Seam Security supports the same kind of "Remember Me" functionality that is commonly encountered in many online web-based applications. It is actually supported in two different "flavours", or modes - the first mode allows the username to be stored in the user's browser as a cookie, and leaves the entering of the password up to the browser (many modern browsers are capable of remembering passwords).

The second mode supports the storing of a unique token in a cookie, and allows a user to authenticate automatically upon returning to the site, without having to provide a password.

Warning

Automatic client authentication with a persistent cookie stored on the client machine is dangerous. While convenient for users, any cross-site scripting security hole in your website would have dramatically more serious effects than usual. Without the authentication cookie, the only cookie to steal for an attacker with XSS is the cookie of the current session of a user. This means the attack only works when the user has an open session - which should be a short timespan. However, it is much more attractive and dangerous if an attacker has the possibility to steal a persistent Remember Me cookie that allows him to login without authentication, at any time. Note that this all depends on how well you protect your website against XSS attacks - it's up to you to make sure that your website is 100% XSS safe - a non-trivial achievement for any website that allows user input to be rendered on a page.

Browser vendors recognized this issue and introduced a "Remember Passwords" feature - today almost all browsers support this. Here, the browser remembers the login username and password for a particular website and domain, and fills out the login form automatically when you don't have an active session with the website. If you as a website designer then offer a convenient login keyboard shortcut, this approach is almost as convenient as a "Remember Me" cookie and much safer. Some browsers (e.g. Safari on OS X) even store the login form data in the encrypted global operation system keychain. Or, in a networked environment, the keychain can be transported with the user (between laptop and desktop for example), while browser cookies are usually not synchronized.

To summarize: While everyone is doing it, persistent "Remember Me" cookies with automatic authentication are a bad practice and should not be used. Cookies that "remember" only the users login name, and fill out the login form with that username as a convenience, are not an issue.

To enable the remember me feature for the default (safe, username only) mode, no special configuration is required. In your login form, simply bind the remember me checkbox to rememberMe.enabled, like 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>

To use the automatic, token-based mode of the remember me feature, you must first configure a token store. The most common scenario is to store these authentication tokens within a database (which Seam supports), however it is possible to implement your own token store by implementing the org.jboss.seam.security.TokenStore interface. This section will assume you will be using the provided JpaTokenStore implementation to store authentication tokens inside a database table.

The first step is to create a new Entity which will contain the tokens. The following example shows a possible structure that you may use:

@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;
   }
}

As you can see from this listing, a couple of 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 will contain the authentication tokens.

The next step is to configure JpaTokenStore to use this entity bean to store and retrieve authentication tokens. This is done in components.xml by specifying the token-class attribute:



<security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken" />
        

Once this is done, the last thing to do is to configure the RememberMe component in components.xml also. Its mode should be set to autoLogin:



<security:remember-me mode="autoLogin"/>
        

That is all that is required - automatic authentication will now occur for users revisiting your site (as long as they check the "remember me" checkbox).

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>

To prevent users from receiving the default error page in response to a security error, it's recommended that pages.xml is configured to redirect security errors to a more "pretty" page. The two main types of exceptions thrown by the security API are:

In the case of a NotLoggedInException, it is recommended that the user is redirected to either 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 even more sophisticated handling of login redirection, so Seam includes some special functionality for handling this problem.

Although not recommended for use unless absolutely necessary, Seam provides means for authenticating using either HTTP Basic or HTTP Digest (RFC 2617) methods. To use either form of authentication, the authentication-filter component must be enabled in components.xml:



  <web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
      

To enable the filter for basic authentication, set auth-type to basic, or for digest authentication, set it to digest. If using digest authentication, the key and realm must also be set:



  <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.

Identity Management provides a standard API for the management of a Seam application's users and roles, regardless of which identity store (database, LDAP, etc) is used on the backend. At the center of the Identity Management API is the identityManager component, which provides all the methods for creating, modifying and deleting users, granting and revoking roles, changing passwords, enabling and disabling user accounts, authenticating users and listing users and roles.

Before it may be used, the identityManager must first be configured with one or more IdentityStores. These components do the actual work of interacting with the backend security provider, whether it be a database, LDAP server, or something else.

The identityManager component allows for separate identity stores to be configured for authentication and authorization operations. This means that it is possible for users to be authenticated against one identity store, for example an LDAP directory, yet have their roles loaded from another identity store, such as a relational database.

Seam provides two IdentityStore implementations out of the box; JpaIdentityStore uses a relational database to store user and role information, and is the default identity store that is used if nothing is explicitly configured in the identityManager component. The other implementation that is provided is LdapIdentityStore, which uses an LDAP directory to store users and roles.

There are two configurable properties for the identityManager component - identityStore and roleIdentityStore. The value for these properties must be an EL expression referring to a Seam component implementing the IdentityStore interface. As already mentioned, if left unconfigured then JpaIdentityStore will be assumed by default. If only the identityStore property is configured, then the same value will be used for roleIdentityStore also. For example, the following entry in components.xml will configure identityManager to use an LdapIdentityStore for both user-related and role-related operations:


      
  <security:identity-manager identity-store="#{ldapIdentityStore}"/>
      

The following example configures identityManager to use an LdapIdentityStore for user-related operations, and JpaIdentityStore for role-related operations:


      
  <security:identity-manager 
    identity-store="#{ldapIdentityStore}" 
    role-identity-store="#{jpaIdentityStore}"/>
      

The following sections explain both of these identity store implementations in greater detail.

This identity store allows for users and roles to be stored inside a relational database. It is designed to be as unrestrictive as possible in regards to database schema design, allowing a great deal of flexibility in the underlying table structure. This is achieved through the use of a set of special annotations, allowing entity beans to be configured to store user and role records.

As already mentioned, a set of special annotations are used to configure entity beans for storing users and roles. The following table lists each of the annotations, and their descriptions.



As mentioned previously, JpaIdentityStore is designed to be as flexible as possible when it comes to the database schema design of your user and role tables. This section looks at a number of possible database schemas that can be used to store user and role records.

In this bare minimal example, a simple user and role table are linked via a many-to-many relationship using a cross-reference table named UserRoles.

@Entity

public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role> roles;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
    joinColumns = @JoinColumn(name = "UserId"),
    inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role> getRoles() { return roles; }
  public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity

public class Role {
  private Integer roleId;
  private String rolename;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
}

This example builds on the above minimal example by including all of the optional fields, and allowing group memberships for roles.

@Entity

public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role> roles;
  private String firstname;
  private String lastname;
  private boolean enabled;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
  
  @UserFirstName
  public String getFirstname() { return firstname; }
  public void setFirstname(String firstname) { this.firstname = firstname; }
  
  @UserLastName
  public String getLastname() { return lastname; }
  public void setLastname(String lastname) { this.lastname = lastname; }
  
  @UserEnabled
  public boolean isEnabled() { return enabled; }
  public void setEnabled(boolean enabled) { this.enabled = enabled; }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
    joinColumns = @JoinColumn(name = "UserId"),
    inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role> getRoles() { return roles; }
  public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity

public class Role {
  private Integer roleId;
  private String rolename;
  private boolean conditional;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
  
  @RoleConditional
  public boolean isConditional() { return conditional; }
  public void setConditional(boolean conditional) { this.conditional = conditional; }
  
  @RoleGroups
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "RoleGroups", 
    joinColumns = @JoinColumn(name = "RoleId"),
    inverseJoinColumns = @JoinColumn(name = "GroupId"))
  public Set<Role> getGroups() { return groups; }
  public void setGroups(Set<Role> groups) { this.groups = groups; }  
  
}

This identity store implementation is designed for working with user records stored in an LDAP directory. It is very highly configurable, allowing great flexibility in how both users and roles are stored in the directory. The following sections describe the configuration options for this identity store, and provide some configuration examples.

The following table describes the available properties that can be configured in components.xml for LdapIdentityStore.

Table 16.3. LdapIdentityStore Configuration Properties

Property

Default Value

Description

server-address

localhost

The address of the LDAP server.

server-port

389

The port number that the LDAP server is listening on.

user-context-DN

ou=Person,dc=acme,dc=com

The Distinguished Name (DN) of the context containing user records.

user-DN-prefix

uid=

This value is prefixed to the front of the username to locate the user's record.

user-DN-suffix

,ou=Person,dc=acme,dc=com

This value is appended to the end of the username to locate the user's record.

role-context-DN

ou=Role,dc=acme,dc=com

The DN of the context containing role records.

role-DN-prefix

cn=

This value is prefixed to the front of the role name to form the DN for locating the role record.

role-DN-suffix

,ou=Roles,dc=acme,dc=com

This value is appended to the role name to form the DN for locating the role record.

bind-DN

cn=Manager,dc=acme,dc=com

This is the context used to bind to the LDAP server.

bind-credentials

secret

These are the credentials (the password) used to bind to the LDAP server.

user-role-attribute

roles

This is the name of the attribute of the user record that contains the list of roles that the user is a member of.

role-attribute-is-DN

true

This boolean property indicates whether the role attribute of the user record is itself a distinguished name.

user-name-attribute

uid

Indicates which attribute of the user record contains the username.

user-password-attribute

userPassword

Indicates which attribute of the user record contains the user's password.

first-name-attribute

null

Indicates which attribute of the user record contains the user's first name.

last-name-attribute

sn

Indicates which attribute of the user record contains the user's last name.

full-name-attribute

cn

Indicates which attribute of the user record contains the user's full (common) name.

enabled-attribute

null

Indicates which attribute of the user record determines whether the user is enabled.

role-name-attribute

cn

Indicates which attribute of the role record contains the name of the role.

object-class-attribute

objectClass

Indicates which attribute determines the class of an object in the directory.

role-object-classes

organizationalRole

An array of the object classes that new role records should be created as.

user-object-classes

person,uidObject

An array of the object classes that new user records should be created as.

security-authentication-type

simple

The security level to use. Possible values are "none", "simple" and "strong".


The IdentityManager can be accessed either by injecting it into your Seam component as follows:

  @In IdentityManager identityManager;

or by accessing it through its static instance() method:

  IdentityManager identityManager = IdentityManager.instance();

The following table describes IdentityManager's API methods:

Table 16.4. Identity Management API

Method

Returns

Description

createUser(String name, String password)

boolean

Creates a new user account, with the specified name and password. Returns true if successful, or false if not.

deleteUser(String name)

boolean

Deletes the user account with the specified name. Returns true if successful, or false if not.

createRole(String role)

boolean

Creates a new role, with the specified name. Returns true if successful, or false if not.

deleteRole(String name)

boolean

Deletes the role with the specified name. Returns true if successful, or false if not.

enableUser(String name)

boolean

Enables the user account with the specified name. Accounts that are not enabled are not able to authenticate. Returns true if successful, or false if not.

disableUser(String name)

boolean

Disables the user account with the specified name. Returns true if successful, or false if not.

changePassword(String name, String password)

boolean

Changes the password for the user account with the specified name. Returns true if successful, or false if not.

isUserEnabled(String name)

boolean

Returns true if the specified user account is enabled, or false if it isn't.

grantRole(String name, String role)

boolean

Grants the specified role to the specified user or role. The role must already exist for it to be granted. Returns true if the role is successfully granted, or false if it is already granted to the user.

revokeRole(String name, String role)

boolean

Revokes the specified role from the specified user or role. Returns true if the specified user is a member of the role and it is successfully revoked, or false if the user is not a member of the role.

userExists(String name)

boolean

Returns true if the specified user exists, or false if it doesn't.

listUsers()

List

Returns a list of all user names, sorted in alpha-numeric order.

listUsers(String filter)

List

Returns a list of all user names filtered by the specified filter parameter, sorted in alpha-numeric order.

listRoles()

List

Returns a list of all role names.

getGrantedRoles(String name)

List

Returns a list of the names of all the roles explicitly granted to the specified user name.

getImpliedRoles(String name)

List

Returns a list of the names of all the roles implicitly granted to the specified user name. Implicitly granted roles include those that are not directly granted to a user, rather they are granted to the roles that the user is a member of. For example, is the admin role is a member of the user role, and a user is a member of the admin role, then the implied roles for the user are both the admin, and user roles.

authenticate(String name, String password)

boolean

Authenticates the specified username and password using the configured Identity Store. Returns true if successful or false if authentication failed. Successful authentication implies nothing beyond the return value of the method. It does not change the state of the Identity component - to perform a proper Seam login the Identity.login() must be used instead.

addRoleToGroup(String role, String group)

boolean

Adds the specified role as a member of the specified group. Returns true if the operation is successful.

removeRoleFromGroup(String role, String group)

boolean

Removes the specified role from the specified group. Returns true if the operation is successful.

listRoles()

List

Lists the names of all roles.


Using the Identity Management API requires that the calling user has the appropriate authorization to invoke its methods. The following table describes the permission requirements for each of the methods in IdentityManager. The permission targets listed below are literal String values.


The following code listing provides an example set of security rules that grants access to all Identity Management-related methods to members of the admin role:

rule ManageUsers
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.user", granted == false)
  Role(name == "admin")
then
  check.grant();
end

rule ManageRoles
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.role", granted == false)
  Role(name == "admin")
then
  check.grant();
end

The security API produces a number of default faces messages for various security-related events. The following table lists the message keys that can be used to override these messages by specifying them in a message.properties resource file. To suppress the message, just put the key with an empty value in the resource file.


There are a number of authorization mechanisms provided by the Seam Security API for securing access to components, component methods, and pages. This section describes each of these. An important thing to note is that if you wish to use any of the advanced features (such as rule-based permissions) then your components.xml may need to be configured to support this - see the Configuration section above.

Seam Security is built around the premise of users being granted roles and/or permissions, allowing them to perform operations that may not otherwise be permissible for users without the necessary security privileges. Each of the authorization mechanisms provided by the Seam Security API are built upon this core concept of roles and permissions, with an extensible framework providing multiple ways to secure application resources.

Let's start by examining the simplest form of authorization, component security, starting with the @Restrict annotation.

Seam components may be secured either at the method or the class level, using the @Restrict annotation. If both a method and it's declaring class are annotated with @Restrict, the method restriction will take precedence (and the class restriction will not apply). If a method invocation fails a security check, then an exception will be thrown as per the contract for Identity.checkRestriction() (see Inline Restrictions). A @Restrict on just the component class itself is equivalent to adding @Restrict to each of its methods.

An empty @Restrict implies a permission check of componentName:methodName. Take for example the following component method:

@Name("account")

public class AccountAction {
    @Restrict public void delete() {
      ...
    }
}

In this example, the implied permission required to call the delete() method is account:delete. The equivalent of this would be to write @Restrict("#{s:hasPermission('account','delete')}"). Now let's look at another example:

@Restrict @Name("account")

public class AccountAction {
    public void insert() {
      ...
    }
    @Restrict("#{s:hasRole('admin')}")
    public void delete() {
      ...
    }
}

This time, the component class itself is annotated with @Restrict. This means that any methods without an overriding @Restrict annotation require an implicit permission check. In the case of this example, the insert() method requires a permission of account:insert, while the delete() method requires that the user is a member of the admin role.

Before we go any further, let's address the #{s:hasRole()} expression seen in the above example. Both s:hasRole and s:hasPermission are EL functions, which delegate to the correspondingly named methods of the Identity class. These functions can be used within any EL expression throughout the entirety of the security API.

Being an EL expression, the value of the @Restrict annotation may reference any objects that exist within a Seam context. This is extremely useful when performing permission checks for a specific object instance. Look at this example:

@Name("account")

public class AccountAction {
    @In Account selectedAccount;
    @Restrict("#{s:hasPermission(selectedAccount,'modify')}")
    public void modify() {
        selectedAccount.modify();
    }
}

The interesting thing to note from this example is the reference to selectedAccount seen within the hasPermission() function call. The value of this variable will be looked up from within the Seam context, and passed to the hasPermission() method in Identity, which in this case can then determine if the user has the required permission for modifying the specified Account object.

One indication of a well designed user interface is that the user is not presented with options for which they don't have the necessary privileges to use. Seam Security allows conditional rendering of either 1) sections of a page or 2) individual controls, based upon the privileges of the user, using the very same EL expressions that are used for component security.

Let's take a look at some examples of interface security. First of all, let's pretend that we have a login form that should only be rendered if the user is not already logged in. Using the identity.isLoggedIn() property, we can write this:


<h:form class="loginForm" rendered="#{not identity.loggedIn}">

If the user isn't logged in, then the login form will be rendered - very straight forward so far. Now let's pretend there is a menu on the page that contains some actions which should only be accessible to users in the manager role. Here's one way that these could be written:


<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">
    Manager Reports
</h:outputLink>

This is also quite straight forward. If the user is not a member of the manager role, then the outputLink will not be rendered. The rendered attribute can generally be used on the control itself, or on a surrounding <s:div> or <s:span> control.

Now for something more complex. Let's say you have a h:dataTable control on a page listing records for which you may or may not wish to render action links depending on the user's privileges. The s:hasPermission EL function allows us to pass in an object parameter which can be used to determine whether the user has the requested permission for that object or not. Here's how a dataTable with secured links might look:


<h:dataTable value="#{clients}" var="cl">
    <h:column>
        <f:facet name="header">Name</f:facet>
        #{cl.name}
    </h:column>
    <h:column>
        <f:facet name="header">City</f:facet>
        #{cl.city}
    </h:column>
    <h:column>
        <f:facet name="header">Action</f:facet>
        <s:link value="Modify Client" action="#{clientAction.modify}"
                rendered="#{s:hasPermission(cl,'modify')}"/>
        <s:link value="Delete Client" action="#{clientAction.delete}"
                rendered="#{s:hasPermission(cl,'delete')}"/>
    </h:column>
</h:dataTable>

Seam security also makes it possible to apply security restrictions to read, insert, update and delete actions for entities.

To secure all actions for an entity class, add a @Restrict annotation on the class itself:

@Entity

@Name("customer")
@Restrict
public class Customer {
  ...
}

If no expression is specified in the @Restrict annotation, the default security check that is performed is a permission check of entity:action, where the permission target is the entity instance, and the action is either read, insert, update or delete.

It is also possible to only restrict certain actions, by placing a @Restrict annotation on the relevant entity lifecycle method (annotated as follows):

Here's an example of how an entity would be configured to perform a security check for any insert operations. Please note that the method is not required to do anything, the only important thing in regard to security is how it is annotated:



  @PrePersist @Restrict
  public void prePersist() {}
   

And here's an example of an entity permission rule that checks if the authenticated user is allowed to insert a new MemberBlog record (from the seamspace example). The entity for which the security check is being made is automatically inserted into the working memory (in this case MemberBlog):

rule InsertMemberBlog
  no-loop
  activation-group "permissions"
when
  principal: Principal()
  memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName())))
  check: PermissionCheck(target == memberBlog, action == "insert", granted == false)
then
  check.grant();
end;

This rule will grant the permission memberBlog:insert if the currently authenticated user (indicated by the Principal fact) has the same name as the member for which the blog entry is being created. The "principal: Principal()" structure that can be seen in the example code is a variable binding - it binds the instance of the Principal object from the working memory (placed there during authentication) and assigns it to a variable called principal. Variable bindings allow the value to be referred to in other places, such as the following line which compares the member's username to the Principal name. For more details, please refer to the JBoss Rules documentation.

Finally, we need to install a listener class that integrates Seam security with your JPA provider.

Seam provides a number of annotations that may be used as an alternative to @Restrict, which have the added advantage of providing compile-time safety as they don't support arbitrary EL expressions in the same way that @Restrict does.

Out of the box, Seam comes with annotations for standard CRUD-based permissions, however it is a simple matter to add your own. The following annotations are provided in the org.jboss.seam.annotations.security package:

To use these annotations, simply place them on the method or parameter for which you wish to perform a security check. If placed on a method, then they should specify a target class for which the permission will be checked. Take the following example:

  @Insert(Customer.class)
  public void createCustomer() {
    ...
  }

In this example, a permission check will be performed for the user to ensure that they have the rights to create new Customer objects. The target of the permission check will be Customer.class (the actual java.lang.Class instance itself), and the action is the lower case representation of the annotation name, which in this example is insert.

It is also possible to annotate the parameters of a component method in the same way. If this is done, then it is not required to specify a permission target (as the parameter value itself will be the target of the permission check):

  public void updateCustomer(@Update Customer customer) {
    ...
  }

To create your own security annotation, you simply need to annotate it with @PermissionCheck, for example:

@Target({METHOD, PARAMETER})

@Documented
@Retention(RUNTIME)
@Inherited
@PermissionCheck
public @interface Promote {
   Class value() default void.class;
}

If you wish to override the default permission action name (which is the lower case version of the annotation name) with another value, you can specify it within the @PermissionCheck annotation:

@PermissionCheck("upgrade")

Seam Security provides an extensible framework for resolving application permissions. The following class diagram shows an overview of the main components of the permission framework:

The relevant classes are explained in more detail in the following sections.

This is actually an interface, which provides methods for resolving individual object permissions. Seam provides the following built-in PermissionResolver implementations, which are described in more detail later in the chapter:

It is very simple to implement your own permission resolver. The PermissionResolver interface defines only two methods that must be implemented, as shown by the following table. By deploying your own PermissionResolver implementation in your Seam project, it will be automatically scanned during deployment and registered with the default ResolverChain.


Note

As they are cached in the user's session, any custom PermissionResolver implementations must adhere to a couple of restrictions. Firstly, they may not contain any state that is finer-grained than session scope (and the scope of the component itself should either be application or session). Secondly, they must not use dependency injection as they may be accessed from multiple threads simultaneously. In fact, for performance reasons it is recommended that they are annotated with @BypassInterceptors to bypass Seam's interceptor stack altogether.

A ResolverChain contains an ordered list of PermissionResolvers, for the purpose of resolving object permissions for a particular object class or permission target.

The default ResolverChain consists of all permission resolvers discovered during application deployment. The org.jboss.seam.security.defaultResolverChainCreated event is raised (and the ResolverChain instance passed as an event parameter) when the default ResolverChain is created. This allows additional resolvers that for some reason were not discovered during deployment to be added, or for resolvers that are in the chain to be re-ordered or removed.

The following sequence diagram shows the interaction between the components of the permission framework during a permission check (explanation follows). A permission check can originate from a number of possible sources, for example - the security interceptor, the s:hasPermission EL function, or via an API call to Identity.checkPermission:

One of the built-in permission resolvers provided by Seam, RuleBasedPermissionResolver allows permissions to be evaluated based on a set of Drools (JBoss Rules) security rules. A couple of the advantages of using a rule engine are 1) a centralized location for the business logic that is used to evaluate user permissions, and 2) speed - Drools uses very efficient algorithms for evaluating large numbers of complex rules involving multiple conditions.

The configuration for RuleBasedPermissionResolver requires that a Drools rule base is first configured in components.xml. By default, it expects that the rule base is named securityRules, as per the following example:


<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:drools="http://jboss.org/schema/seam/drools"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation=
                  "http://jboss.org/schema/seam/core http://jboss.org/schema/seam/core-2.3.xsd
                   http://jboss.org/schema/seam/components http://jboss.org/schema/seam/components-2.3.xsd
                   http://jboss.org/schema/seam/drools http://jboss.org/schema/seam/drools-2.3.xsd
                   http://jboss.org/schema/seam/security http://jboss.org/schema/seam/security-2.3.xsd">
  
     <drools:rule-base name="securityRules">
         <drools:rule-files>
             <value>/META-INF/security.drl</value>
         </drools:rule-files>
     </drools:rule-base>
  
  </components>

The default rule base name can be overridden by specifying the security-rules property for RuleBasedPermissionResolver:

  <security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>

Once the RuleBase component is configured, it's time to write the security rules.

The first step to writing security rules is to create a new rule file in the /META-INF directory of your application's jar file. Usually this file would be named something like security.drl, however you can name it whatever you like as long as it is configured correspondingly in components.xml.

So what should the security rules file contain? At this stage it might be a good idea to at least skim through the Drools documentation, however to get started here's an extremely simple example:

package MyApplicationPermissions;
  
  import org.jboss.seam.security.permission.PermissionCheck;
  import org.jboss.seam.security.Role;
  
  rule CanUserDeleteCustomers
  when
    c: PermissionCheck(target == "customer", action == "delete")
    Role(name == "admin")
  then
    c.grant();
  end

Let's break this down step by step. The first thing we see is the package declaration. A package in Drools is essentially a collection of rules. The package name can be anything you want - it doesn't relate to anything else outside the scope of the rule base.

The next thing we can notice is a couple of import statements for the PermissionCheck and Role classes. These imports inform the rules engine that we'll be referencing these classes within our rules.

Finally we have the code for the rule. Each rule within a package should be given a unique name (usually describing the purpose of the rule). In this case our rule is called CanUserDeleteCustomers and will be used to check whether a user is allowed to delete a customer record.

Looking at the body of the rule definition we can notice two distinct sections. Rules have what is known as a left hand side (LHS) and a right hand side (RHS). The LHS consists of the conditional part of the rule, i.e. a list of conditions which must be satisfied for the rule to fire. The LHS is represented by the when section. The RHS is the consequence, or action section of the rule that will only be fired if all of the conditions in the LHS are met. The RHS is represented by the then section. The end of the rule is denoted by the end line.

If we look at the LHS of the rule, we see two conditions listed there. Let's examine the first condition:

c: PermissionCheck(target == "customer", action == "delete")

In plain english, this condition is stating that there must exist a PermissionCheck object with a target property equal to "customer", and an action property equal to "delete" within the working memory.

So what is the working memory? Also known as a "stateful session" in Drools terminology, the working memory is a session-scoped object that contains the contextual information that is required by the rules engine to make a decision about a permission check. Each time the hasPermission() method is called, a temporary PermissionCheck object, or Fact, is inserted into the working memory. This PermissionCheck corresponds exactly to the permission that is being checked, so for example if you call hasPermission("account", "create") then a PermissionCheck object with a target equal to "account" and action equal to "create" will be inserted into the working memory for the duration of the permission check.

Besides the PermissionCheck facts, there is also a org.jboss.seam.security.Role fact for each of the roles that the authenticated user is a member of. These Role facts are synchronized with the user's authenticated roles at the beginning of every permission check. As a consequence, any Role object that is inserted into the working memory during the course of a permission check will be removed before the next permission check occurs, if the authenticated user is not actually a member of that role. Besides the PermissionCheck and Role facts, the working memory also contains the java.security.Principal object that was created as a result of the authentication process.

It is also possible to insert additional long-lived facts into the working memory by calling RuleBasedPermissionResolver.instance().getSecurityContext().insert(), passing the object as a parameter. The exception to this is Role objects, which as already discussed are synchronized at the start of each permission check.

Getting back to our simple example, we can also notice that the first line of our LHS is prefixed with c:. This is a variable binding, and is used to refer back to the object that is matched by the condition (in this case, the PermissionCheck). Moving on to the second line of our LHS, we see this:

Role(name == "admin")

This condition simply states that there must be a Role object with a name of "admin" within the working memory. As already mentioned, user roles are inserted into the working memory at the beginning of each permission check. So, putting both conditions together, this rule is essentially saying "I will fire if you are checking for the customer:delete permission and the user is a member of the admin role".

So what is the consequence of the rule firing? Let's take a look at the RHS of the rule:

c.grant()

The RHS consists of Java code, and in this case is invoking the grant() method of the c object, which as already mentioned is a variable binding for the PermissionCheck object. Besides the name and action properties of the PermissionCheck object, there is also a granted property which is initially set to false. Calling grant() on a PermissionCheck sets the granted property to true, which means that the permission check was successful, allowing the user to carry out whatever action the permission check was intended for.

Another built-in permission resolver provided by Seam, PersistentPermissionResolver allows permissions to be loaded from persistent storage, such as a relational database. This permission resolver provides ACL style instance-based security, allowing for specific object permissions to be assigned to individual users and roles. It also allows for persistent, arbitrarily-named permission targets (not necessarily object/class based) to be assigned in the same way.

A permission store is required for PersistentPermissionResolver to connect to the backend storage where permissions are persisted. Seam provides one PermissionStore implementation out of the box, JpaPermissionStore, which is used to store permissions inside a relational database. It is possible to write your own permission store by implementing the PermissionStore interface, which defines the following methods:

Table 16.8. PermissionStore interface

Return type

Method

Description

List<Permission>

listPermissions(Object target)

This method should return a List of Permission objects representing all the permissions granted for the specified target object.

List<Permission>

listPermissions(Object target, String action)

This method should return a List of Permission objects representing all the permissions with the specified action, granted for the specified target object.

List<Permission>

listPermissions(Set<Object> targets, String action)

This method should return a List of Permission objects representing all the permissions with the specified action, granted for the specified set of target objects.

boolean

grantPermission(Permission)

This method should persist the specified Permission object to the backend storage, returning true if successful.

boolean

grantPermissions(List<Permission> permissions)

This method should persist all of the Permission objects contained in the specified List, returning true if successful.

boolean

revokePermission(Permission permission)

This method should remove the specified Permission object from persistent storage.

boolean

revokePermissions(List<Permission> permissions)

This method should remove all of the Permission objects in the specified list from persistent storage.

List<String>

listAvailableActions(Object target)

This method should return a list of all the available actions (as Strings) for the class of the specified target object. It is used in conjunction with permission management to build the user interface for granting specific class permissions (see section further down).


This is the default PermissionStore implementation (and the only one provided by Seam), which uses a relational database to store permissions. Before it can be used it must be configured with either one or two entity classes for storing user and role permissions. These entity classes must be annotated with a special set of security annotations to configure which properties of the entity correspond to various aspects of the permissions being stored.

If you wish to use the same entity (i.e. a single database table) to store both user and role permissions, then only the user-permission-class property is required to be configured. If you wish to use separate tables for storing user and role permissions, then in addition to the user-permission-class property you must also configure the role-permission-class property.

For example, to configure a single entity class to store both user and role permissions:


<security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission" />

To configure separate entity classes for storing user and role permissions:


<security:jpa-permission-store user-permission-class="com.acme.model.UserPermission"
    role-permission-class="com.acme.model.RolePermission" />

As mentioned, the entity classes that contain the user and role permissions must be configured with a special set of annotations, contained within the org.jboss.seam.annotations.security.permission package. The following table lists each of these annotations along with a description of how they are used:

Table 16.9. Entity Permission annotations

Annotation

Target

Description

@PermissionTarget

FIELD,METHOD

This annotation identifies the property of the entity that will contain the permission target. The property should be of type java.lang.String.

@PermissionAction

FIELD,METHOD

This annotation identifies the property of the entity that will contain the permission action. The property should be of type java.lang.String.

@PermissionUser

FIELD,METHOD

This annotation identifies the property of the entity that will contain the recipient user for the permission. It should be of type java.lang.String and contain the user's username.

@PermissionRole

FIELD,METHOD

This annotation identifies the property of the entity that will contain the recipient role for the permission. It should be of type java.lang.String and contain the role name.

@PermissionDiscriminator

FIELD,METHOD

This annotation should be used when the same entity/table is used to store both user and role permissions. It identifies the property of the entity that is used to discriminate between user and role permissions. By default, if the column value contains the string literal user, then the record will be treated as a user permission. If it contains the string literal role, then it will be treated as a role permission. It is also possible to override these defaults by specifying the userValue and roleValue properties within the annotation. For example, to use u and r instead of user and role, the annotation would be written like this:

@PermissionDiscriminator

(userValue="u", roleValue="r")

Here is an example of an entity class that is used to store both user and role permissions. The following class can be found inside the SeamSpace example:



@Entity
public class AccountPermission implements Serializable {  
   private Integer permissionId;
   private String recipient;
   private String target;
   private String action;
   private String discriminator;
   
   @Id @GeneratedValue
   public Integer getPermissionId() {
      return permissionId;
   }
   
   public void setPermissionId(Integer permissionId) {
      this.permissionId = permissionId;
   }
   
   @PermissionUser @PermissionRole
   public String getRecipient() {
      return recipient;
   }
   
   public void setRecipient(String recipient) {
      this.recipient = recipient;
   }
   
   @PermissionTarget
   public String getTarget() {
      return target;
   }
   
   public void setTarget(String target) {
      this.target = target;
   }
   
   @PermissionAction
   public String getAction() {
      return action;
   }
   
   public void setAction(String action) {
      this.action = action;
   }
   
   @PermissionDiscriminator
   public String getDiscriminator() {
      return discriminator;
   }
   
   public void setDiscriminator(String discriminator) {
      this.discriminator = discriminator;
   }
}          
          

As can be seen in the above example, the getDiscriminator() method has been annotated with the @PermissionDiscriminator annotation, to allow JpaPermissionStore to determine which records represent user permissions and which represent role permissions. In addition, it can also be seen that the getRecipient() method is annotated with both @PermissionUser and @PermissionRole annotations. This is perfectly valid, and simply means that the recipient property of the entity will either contain the name of the user or the name of the role, depending on the value of the discriminator property.

When storing or looking up permissions, JpaPermissionStore must be able to uniquely identify specific object instances to effectively operate on its permissions. To achieve this, an identifier strategy may be assigned to each target class for the generation of unique identifier values. Each identifier strategy implementation knows how to generate unique identifiers for a particular type of class, and it is a simple matter to create new identifier strategies.

The IdentifierStrategy interface is very simple, declaring only two methods:

public interface IdentifierStrategy {

   boolean canIdentify(Class targetClass);
   String getIdentifier(Object target);
}

The first method, canIdentify() simply returns true if the identifier strategy is capable of generating a unique identifier for the specified target class. The second method, getIdentifier() returns the unique identifier value for the specified target object.

Seam provides two IdentifierStrategy implementations, ClassIdentifierStrategy and EntityIdentifierStrategy (see next sections for details).

To explicitly configure a specific identifier strategy to use for a particular class, it should be annotated with org.jboss.seam.annotations.security.permission.Identifier, and the value should be set to a concrete implementation of the IdentifierStrategy interface. An optional name property can also be specified, the effect of which is dependent upon the actual IdentifierStrategy implementation used.

This identifier strategy is used to generate unique identifiers for entity beans. It does so by concatenating the entity name (or otherwise configured name) with a string representation of the primary key value of the entity. The rules for generating the name section of the identifier are similar to ClassIdentifierStrategy. The primary key value (i.e. the id of the entity) is obtained using the PersistenceProvider component, which is able to correctly determine the value regardless of which persistence implementation is used within the Seam application. For entities not annotated with @Entity, it is necessary to explicitly configure the identifier strategy on the entity class itself, for example:

@Identifier(value = EntityIdentifierStrategy.class)

public class Customer { 

For an example of the type of identifier values generated, assume we have the following entity class:

@Entity

public class Customer {
  private Integer id;
  private String firstName;
  private String lastName;
  
  @Id 
  public Integer getId() { return id; }
  public void setId(Integer id) { this.id = id; }
  
  public String getFirstName() { return firstName; }
  public void setFirstName(String firstName) { this.firstName = firstName; }
  
  public String getLastName() { return lastName; }
  public void setLastName(String lastName) { this.lastName = lastName; }
}

For a Customer instance with an id value of 1, the value of the identifier would be "Customer:1". If the entity class is annotated with an explicit identifier name, like so:

@Entity

@Identifier(name = "cust")
public class Customer { 

Then a Customer with an id value of 123 would have an identifier value of "cust:123".

In much the same way that Seam Security provides an Identity Management API for the management of users and roles, it also provides a Permissions Management API for the management of persistent user permissions, via the PermissionManager component.

The PermissionManager component is an application-scoped Seam component that provides a number of methods for managing permissions. Before it can be used, it must be configured with a permission store (although by default it will attempt to use JpaPermissionStore if it is available). To explicitly configure a custom permission store, specify the permission-store property in components.xml:



<security:permission-manager permission-store="#{ldapPermissionStore}"/>      
      

The following table describes each of the available methods provided by PermissionManager:


Seam includes basic support for serving sensitive pages via the HTTPS protocol. This is easily configured by specifying a scheme for the page in pages.xml. The following example shows how the view /login.xhtml is configured to use HTTPS:


<page view-id="/login.xhtml" scheme="https"/>

This configuration is automatically extended to both s:link and s:button JSF controls, which (when specifying the view) will also render the link using the correct protocol. Based on the previous example, the following link will use the HTTPS protocol because /login.xhtml is configured to use it:


<s:link view="/login.xhtml" value="Login"/>

Browsing directly to a view when using the incorrect protocol will cause a redirect to the same view using the correct protocol. For example, browsing to a page that has scheme="https" using HTTP will cause a redirect to the same page using HTTPS.

It is also possible to configure a default scheme for all pages. This is useful if you wish to use HTTPS for a only few pages. If no default scheme is specified then the normal behavior is to continue use the current scheme. So once the user accessed a page that required HTTPS, then HTTPS would continue to be used after the user navigated away to other non-HTTPS pages. (While this is good for security, it is not so great for performance!). To define HTTP as the default scheme, add this line to pages.xml:


<page view-id="*" scheme="http" />

Of course, if none of the pages in your application use HTTPS then it is not required to specify a default scheme.

You may configure Seam to automatically invalidate the current HTTP session each time the scheme changes. Just add this line to components.xml:


<web:session invalidate-on-scheme-change="true"/>

This option helps make your system less vulnerable to sniffing of the session id or leakage of sensitive data from pages using HTTPS to other pages using HTTP.

Though strictly not part of the security API, Seam provides a built-in CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) algorithm to prevent automated processes from interacting with your application.

The following table describes a number of events (see Chapter 7, Events, interceptors and exception handling) raised by Seam Security in response to certain security-related events.


Sometimes it may be necessary to perform certain operations with elevated privileges, such as creating a new user account as an unauthenticated user. Seam Security supports such a mechanism via the RunAsOperation class. This class allows either the Principal or Subject, or the user's roles to be overridden for a single set of operations.

The following code example demonstrates how RunAsOperation is used, by calling its addRole() method to provide a set of roles to masquerade as for the duration of the operation. The execute() method contains the code that will be executed with the elevated privileges.

    new RunAsOperation() {       

       public void execute() {
          executePrivilegedOperation();
       }         
    }.addRole("admin")
     .run();

In a similar way, the getPrincipal() or getSubject() methods can also be overriden to specify the Principal and Subject instances to use for the duration of the operation. Finally, the run() method is used to carry out the RunAsOperation.

Sometimes it might be necessary to extend the Identity component if your application has special security requirements. The following example (contrived, as credentials would normally be handled by the Credentials component instead) shows an extended Identity component with an additional companyCode field. The install precedence of APPLICATION ensures that this extended Identity gets installed in preference to the built-in Identity.

@Name("org.jboss.seam.security.identity")

@Scope(SESSION)
@Install(precedence = APPLICATION)
@BypassInterceptors
@Startup
public class CustomIdentity extends Identity
{
   private static final LogProvider log = Logging.getLogProvider(CustomIdentity.class);
   private String companyCode;
   public String getCompanyCode()
   {
      return companyCode;
   }
   public void setCompanyCode(String companyCode)
   {
      this.companyCode = companyCode;
   }
   @Override
   public String login()
   {
      log.info("###### CUSTOM LOGIN CALLED ######");
      return super.login();
   }
}

OpenID is a community standard for external web-based authentication. The basic idea is that any web application can supplement (or replace) its local handling of authentication by delegating responsibility to an external OpenID server of the user's choose. This benefits the user, who no longer has to remember a name and password for every web application he uses, and the developer, who is relieved of some of the burden of maintaining a complex authentication system.

When using OpenID, the user selects an OpenID provider, and the provider assigns the user an OpenID. The id will take the form of a URL, for example http://maximoburrito.myopenid.com however, it's acceptable to leave off the http:// part of the identifier when logging into a site. The web application (known as a relying party in OpenID-speak) determines which OpenID server to contact and redirects the user to the remote site for authentication. Upon successful authentication the user is given the (cryptographically secure) token proving his identity and is redirected back to the original web application.The local web application can then be sure the user accessing the application controls the OpenID he presented.

It's important to realize at this point that authentication does not imply authorization. The web application still needs to make a determination of how to use that information. The web application could treat the user as instantly logged in and give full access to the system or it could try and map the presented OpenID to a local user account, prompting the user to register if he hasn't already. The choice of how to handle the OpenID is left as a design decision for the local application.