Product SiteDocumentation Site

2.5. Multi-Level Authentication

Most systems rely on a single and unique security level to access their services. Once the user authenticates, he can access any service he is authorized regardless if that service provides any private or sensitive data that needs a more strong identity verification of the user. This is usually done by applying RBAC (role-based access control) or any other type of authorization policy (see Chapter 11, Authorization for more examples).
However, a single security level is not enough if you need a more fine-grained access control to your services, specially for those that maintain private and sensitive data such as transaction confirmation, purchases, change password or edit profile information, administrative operations and so forth. In these cases, you may also want to confirm the identity of an authenticated user by asking a different and more strong credential (eg.: TOTOP tokens, pseudo-random numbers sent to a smartphone, etc) before moving forward.
A security level let you determine how strong the identity of an user is or if he is really the user he claims to be. It is very clear that an user logged from inner company network is more trustworthy then someone logged in from the internet. The same applies if the user was authenticated using biometric or a ordinary username and password credential. Considering that, security levels let you determine different levels of security to your application based on the contextual information about an user (eg.: network, client device, etc) or even based on the supported credentials and authentication methods.
PicketLink Multi-Level Authentication support allows you to define, assign levels to your users and protect your services based on different strategies:
  • Contextual-based
  • Credential-based
  • Authenticator-based
By default, PicketLink sets a default level for every single user with a value 1. Even if the user is not authenticated, he can be assigned with a specific level. Current security level is stored in the Identity bean and can be obtained as follows:
@Inject
private Identity identity;

public void accessConfidentialInfo() {
    Level level = this.identity.getLevel();
	
    if (level.compareTo(getConfidentialLevel()) >= 0)) {
        // user is allowed to access confidential stuff
    }
}
As you may notice, the code above is returning the security level assigned to an user using this.identity.getLevel(). This method returns an instance of Level that represents the level itself. By default, PicketLink create level instances from the DefaultLevel type. However, PicketLink also allows you to provide your own security level types and factory, as we will see later along this book.
The default security level is used when none of the level resolvers were able to resolve the Level of the user. You can provide a default security level value by just producing a Level using the @DefaultSecurityLevel annotation qualifier:
@PicketLink
@DefaultSecurityLevel
@Produces
public Level getDefaultSecurityLevel() {
    return MyDefaultLevel(10);
}
User is granted with a security level at two moments:
  • When identity.getLevel() is called and security level is not resolved yet (for example for unauthenticated user).
    When identity.login() is called. In this case the level will be resolved based on the credential, authenticator or any other contextual level resolver.

Note

One of the most important things you should keep in mind when dealing with security levels is that an user does not need to logout in order to increase his level. This can be achieved any time during user interaction with your application.
The diagram bellow summarizes how users are assigned with a level during the authentication, in other words, when Identity.login is invoked.
  • 1 - The user invokes the login() method of the Identity bean. If the user is logged in, PicketLink will always check if the user is really trying to raise his level. If that is not the case, the Identity bean will throw a UserAlreadyLoggedInException.
  • 1.1 - The Identity bean authenticates the user. At this momment, PicketLink will also check if the user is already authenticated. If so, it will check if the user performing the authentication is the same that was previously authenticated. If that is not the case, the Identity bean will throw a DifferentUserLoggedInExcpetion.
  • 1.2 - If user authentication is successful, the Identity bean try to resolve the security level by invoking the resolve method on each SecurityLevelResolver registered by the application. At this moment, the highest security level resolved from the resolvers is assigned to the user.
  • 2 - The user retrieve the security level from the Identity bean.
On the next sections we will take a closer look on how to extend PicketLink in order to resolve levels based on your own requirements and also how to create your own level types.

2.5.1. Security Level Resolver

As previously discussed, security levels are determined by calling all implementations of the SecurityLevelResolver interface registered by your application. The highest level of all implementation is then determined as the current level for the user. That is the rule.
SecurityLevelResolver implementations are just regular CDI beans as follows:
@RequestScoped
public class CustomSecurityLevelResolver implements SecurityLevelResolver {

    @Inject
    private DefaultLoginCredentials defaultLoginCredentials;

    @Override
    public Level resolve() {
        Object currentCredential = this.defaultLoginCredentials.getCredential();

        if (TOTPCredentials.class.isInstance(currentCredential)) {
            return new DefaultLevel(2);
        }

        return null;
    }
}
Resolvers must implement a single resolve method where all the logic to determine the proper security level resides. In the example above, your application will determine a level 2 when the user is being authenticated using a TOTP credential. In other words, if he proved his identity using two-factor authentication his level will be raised to 2.
You can use whatever logic you want to resolve security levels from a resolver. For instance, gather information from the HttpServletRequest (if you are in a web application), time-based decisions and so forth. Just remember that only the highest level will be assigned to the user.
However, PicketLink also provides some built-in resolvers to support some very common use cases. In both cases, you can use the @SecurityLevel annotation to specify a security level for both authenticators or credentials.
  • Authenticator Security Level Resolver
  • Credential Security Level Resolver
The built-in Authenticator Security Level Resolver automatically resolves security levels from the Authenticator (see Section 2.3.1, “A Basic Authenticator”) used to authenticate an user. That said, if you have created your own authenticators you can just define a security level for them as follows:
@SecurityLevel("3")
@PicketLink
class MyAuthenticator extends BaseAuthenticator {
...
}
The same thing applies to the Credential Security Level Resolver. However, in this case, PicketLink will resolve the security level from the credential used to authenticate an user.
@SecurityLevel("4")
public class MyCredential {
...
}
Whatever you set in DefaultLoginCredentials.setCredential during user authentication, PicketLink will try to resolve the security level from the @SecurityLevel annotation defined on the type of a credential instance.