Signon Overview

This document provides reference information about the KeyMaster SignOn framework and examples of how to use it.

You use the SignOn framework to implement a web-based sign-on system. The implementation uses Java Servlets and Filters to protect the generation of Caplin KeyMaster credentials tokens. It can also protect other web resources as required. The framework is supplied as a class library that you extend to implement single-factor user authentication (1FA) and, if required, two-factor authentication (2FA). The extensions you implement would typically make use of the user authentication and 2FA services provided by your existing security system.

The framework consists of three main components:

  • A SignOn servlet
  • An Authentication Filter
  • A KeyMaster servlet

User authentication is handled by the SignOn servlet. This calls the security system to check user credentials, such as username and password. When a user has been successfully authenticated, their username and authentication level are stored in the servlet session for later use by the Authentication Filter. The servlet receives URL requests from the client; any request parameters must be supplied in JSON format, and it also returns the responses in JSON format. To implement your SignOn servlet, you extend the AbstractSignOnServlet class to implement the abstract AbstractSignOnServlet#authenticate and AbstractSignOnServlet#sendToken methods.

The Authentication Filter sits above resources, such as the KeyMaster servlet, and protects each resource by checking that the user�s session is authenticated to the required level before passing any URL request on to the resource.

The KeyMaster servlet, which is protected by the Authentication Filter, generates credentials tokens that are used to log authenticated users on to Liberator. At the sign-on stage, you can add additional data about the session; the KeyMaster servlet can then extract this data and include it in the generated credentials token.

For a diagram showing the SignOn framework components and how they interact, see KeyMaster SignOn framework

On the rest of this page:

Message flows

Here are some examples of the typical sign-on message flows between a client and the web server that hosts the SignOn framework. The first example shows the message flows for 1FA authentication (authentication of username and password), and the second shows a typical, more complicated sequence for 2FA authentication.

One Factor Authentication (1FA) Message Flow

Two Factor Authentication (2FA) Message Flow

Implementing the SignOn servlet

The KeyMaster kit contains an AbstractSignOnServlet. To implement the SignOn servlet, subclass AbstractSignonServlet and implement the abstract methods: AbstractSignOnServlet#authenticate and AbstractSignOnServlet#sendToken. The reference documentation for these methods shows example implementations of them.

There are also two examples of the SignOn servlet supplied in the KeyMaster kit:

  • The SimpleSignonServlet demonstrates how to perform two-factor authentication on a user by using SMS.
  • The EncryptedSignonServlet shows how a password and SMS token can be encrypted by using a public/private key pair and a JavaScript encryption library.

Request handling

The AbstractSignOnServlet provided in the KeyMaster kit handles the following URL requests. For details of the JSON format parameters that a client must send with URL requests to the SignOn servlet, and the JSON format parameters that the servlet sends in its responses back to the client, see SignOn servlet JSON Specifications.

URL Request Description
/parameters

Requests any signon parameters from the server, the default implementation returns a list of authentication schemes (the default being ['USER'], meaning signon using user and password).

Additional schemes (e.g "SMS") and parameters can be added by the signon servlet using AbstractSignOnServlet#addExtraParameters() and AbstractSignOnServlet#addAuthScheme() methods. Scheme names are strings, but the common values are available as constants with names of the form SessionData.SCHEME_<SCHEME_NAME>. See the table in Authentication schemes below.

You can also add additional parameters to the response, by calling the AbstractSignOnServlet�s AbstractSignonServlet#addExtraParameters() method.

/authenticate

Requests authentication with the supplied authentication scheme, (optional) username, (optional) password and (optional) 2FA token.

The AbstractSignonServlet parses these fields from the request JSON body and then calls your implementation of the AbstractSignonServlet#authenticate() method to actually check the values against your security system. Your implementation should validate the request parameters and either reject the request, by calling AbstractSignonServlet#sendAuthenticateError(), or update the passed in session data with the authenticated username and authentication level and then call AbstractSignonServlet#sendAuthenticateOK().

Authentication levels are strings but the common values are available as constants with names of the form SessionData.LEVEL_<LEVEL_NAME> See the table in Authentication levels below.

If authentication requires a further step (for example, 2FA using SMS) then you can optionally supply a next step parameter to sendAuthenticationOK(). The client is informed of this via the next_step field of the response (see the SignOn servlet JSON Specifications below).

/sendtoken

Where 2FA authentication is required through sending a token using SMS, or a similar method, use the /sendtoken request to ask the server for the 2FA token. The AbstractSignonServlet parses the passed-in username and requested scheme (for example, SMS) and calls your implementation of the AbstractSignonServlet#sendToken() method.

sendToken() should:

  • Look up the user on the security system (for example, to retrieve the phone number to which an SMS message must be sent).
  • Generate the 2FA token
  • Send the 2FA token back to the client using the scheme requested (for example, by generating an SMS message containing the 2FA token and sending the SMS to the retrieved telephone number).
  • Return a JSON response parameter indicating the success or failure of the request.
/logout

Requests the user be logged out and their session data discarded.

any other request

Any other requests are directed to the AbstractSignOnServlet�s AbstractSignonServlet#doOtherRequest() handler. Override this handler to deal with your own specific other request types. For example, you may want to handle password change requests.

-----------------------------------------------------------------------------------------------

Authentication schemes

The following table lists some commonly used authentication schemes. The values are returned by the default implementation of AbstractSignOnServlet in response to the /parameter URL request. (Strings are used so that your implementation of AbstractSignonServlet#authenticate() can return additional authentication schemes.)

String Value Constant Description
USER SessionData.SCHEME_USER 1FA signon using username and password
SMS SessionData.SCHEME_SMS 2FA signon with SMS
TOKEN SessionData.SCHEME_TOKEN 2FA signon with hardware token

Authentication levels

This table lists commonly used authentication levels (Strings are used so that your implementation of AbstractSignonServlet#authenticate() can return a different authentication level to one of the standard ones.)

String Value Constant Description
1FA SessionData.LEVEL_1FA User is signed on to the 1FA level
2FA SessionData.LEVEL_2FA User is signed on to the 2FA level

SignOn servlet JSON Specifications

Here are the specifications of the JSON format parameters that a client must send with URL requests to the SignOn servlet, and the JSON format parameters that the servlet sends in its responses back to the client.

/parameters request

Requests sign-on parameters

Request JSON parameters

None.

Response JSON parameters

Field Description
result The result of request SUCCESS or FAILURE
schemes Array of authentication schemes. A scheme can be any string value, but the default implementation of the SignOn servlet returns the array USER,SMS,TOKEN

You can add additional authentication schemes (such as �SMS�) by calling the AbstractSignOnServlet�s AbstractSignonServlet#addAuthScheme() method. Scheme names are strings, but the common values are available as constants with names of the form SessionData.SCHEME_<SCHEME_NAME>.

See the table in Authenication Schemes below.

You can also add additional parameters to the response, by calling the AbstractSignOnServlet�s AbstractSignonServlet#addExtraParameters() method.

/authenticate request

Requests authentication with the supplied authentication scheme, (optional) username, (optional) password and (optional) 2FA token.

Request JSON parameters

Field Description
scheme The authentication scheme to be used. This can be any string value recognised by your implementation of the SignOn servlet, but values accepted by the default implementation are USER, SMS and TOKEN
username The username to be authenticated [optional, as you can instead obtain the username from the Session object passed into AbstractSignonServlet#authenticate()]
password The user�s password [optional]
token The 2FA token [optional]

Response JSON parameters

Field Description
result The result of the request: SUCCESS or FAILURE
code A code indicating the reason for a FAILURE result.
This can be any string value generated by your implementation of the SignOn servlet, but the values that can be returned by the default implementation are INVALID_CREDENTIALS and ERROR_SERVER
reason A text string giving more information about why the authentication request failed (only present if result is FAILURE).
level The level authenticated to (only present if result is SUCCESS) See Authenication Levels for a list of commonly used authentication levels.
next_step The next step required to authenticate the user [optional]. This can be any string value, but a typical value is 2FA, meaning �proceed to the second step of two-factor authentication�.

/sendtoken request

Asks the SignOn servlet to send a 2FA token applicable to a specified authentication scheme.

Request JSON parameters

Field Description
scheme The authentication scheme to which the requested token applies. This can be any string value but schemes that are supported by the default implementation of the SignOn servlet are listed in Authenication Schemes for example, SMS
username The username for whom the token is required.

Response JSON parameters

Field Description
result The result of the request: SUCCESS or FAILURE
code A code indicating the reason for a FAILURE result.
This can be any string value generated by your implementation of the SignOn servlet, but the values that can be returned by the
reason A text string giving more information about why the token request failed (only present if result is FAILURE).
message A message sent only if result is SUCCESS

/logout request

Logs the user out of their current session.

Request JSON parameters

None.

Response JSON parameters

Field Description
result The result of the request: SUCCESS or FAILURE

Example URL requests and responses

Here are some examples of URL requests that can be sent to the SignOn servlet, and typical successful responses to them:

Request POST body Response body
/parameters none {'result':'SUCCESS','schemes':['USER','SMS']}
/authenticate {'scheme':'USER','username':'user1','password':'invalidpassword'} {'result':'FAILURE','code':'INVALID_CREDENTIALS','reason':'Invalid signon'}
/authenticate {'scheme':'USER','username':'user1','password':'password1'} {'result':'SUCCESS','level':'1FA','next_step':'2FA'}
/sendtoken {'scheme':'SMS','username':'user1'} {'result':'FAILURE','code':'SERVER_ERROR','reason':'Session does not contain username'}
/sendtoken {'scheme':'SMS','username':'user1'} {'result':'SUCCESS','message':'SMS token has been sent to 1234567890'}
/logout none {'result':'SUCCESS'}

Configuring the Authentication Filter

The Authentication Filter is a Servlet filter that intercepts requests from a client before they access a resource, such as the KeyMaster servlet, and checks that the user�s session has been authenticated to the required level. The filter uses the authentication level saved by the SignOn servlet to determine if the request should be passed to the underlying resource.

You must configure in the web application�s web.xml file the URLs, the list of servlets to be protected by the Authentication filter, and the authentication levels for which access is granted to those servlets.

The following snippet of web.xml shows configures the authentication filter to allow access the KeyMaster servlet for sessions authenticated to the '2FA' level; that is, only sessions for users who have successfully logged on using two-factor authentication can access the KeyMaster servlet to obtain a credentials token for accessing Liberator.


        <filter>
                <filter-name>AuthenticationFilter</filter-name>
                <filter-class>com.caplin.signon.AuthenticationFilter</filter-class>
                <init-param>
                        <param-name>allowed.auth.levels</param-name>
                        <param-value>2FA</param-value>
                </init-param>
        </filter>

        <filter-mapping>
                <filter-name>AuthenticationFilter</filter-name>
                <url-pattern>/keymaster</url-pattern>
        </filter-mapping>        

Configuring the KeyMaster servlet

The KeyMaster servlet generates KeyMaster credentials tokens that are used to sign authenticated users on to the Liberator server. Since the KeyMaster servlet is protected behind the Authentication Filter it only receives requests from users who have been authenticated to the desired level.

This KeyMaster servlet uses the following data about the user�s session that was previously saved by the SignOn servlet in the SessionData object:

SessionData Field Description
Username The username of the authenticated user
KeyMasterExtraDataToSign A string of extra data to be added to the credentials token and digitally signed [optional]
KeyMasterMappingData A map of extra name-value pairs to be added to the credentials token [optional]

The KeyMaster servlet parameters:

You configure the KeyMaster servlet by specifying the following Servlet parameters:

OptionRequired
/Optional
Description
caplin.keymaster.privatekey.filenameRequiredThe location of the PEM PKCS#8 formatted private key file relative to the webapp.
caplin.keymaster.hashing.algorithmOptional Hashing algorithm name, see KeyMasterHashingAlgorithm for the possible values. The default is SHA256
caplin.keymaster.security.provider.class.nameOptionalThe class name of a security provider to add to those available.
caplin.keymaster.security.provider.nameOptionalThe name of security provider to use for the token generation.

The KeyMaster servlet web.xml configuration

Here�s a typical example of the web.xml configuration needed for the KeyMaster servlet.


        <servlet>
                <servlet-name>KeyMaster</servlet-name>
                <servlet-class>com.caplin.signon.KeyMasterServlet</servlet-class>

                <init-param>
                        <param-name>caplin.keymaster.privatekey.filename</param-name>
                        <param-value>WEB-INF/private.pem</param-value>
                        <description>Name of the private key filename</description>
                </init-param>
        </servlet>

Implementing a Signon System using the Framework

The KeyMaster kit includes a signon jar containing the AbstractSignonServlet, AuthenticationFilter and KeyMasterServlet.

To implement a signon system based on this framework you must:

  1. Extend the AbstractSignonServlet and implement the authenticate and sendToken methods
  2. Package your SignonServlet in a war with the Signon and KeyMaster jars included in the lib directory
  3. Configure the Servlets and authentication filter in the web.xml.

Example web.xml from simple example:

<web-app>

        <display-name>Simple Signon Example</display-name>
        <description>Simple Signon Example</description>

        <servlet>
                <servlet-name>SimpleSignon</servlet-name>
                <servlet-class>com.caplin.signon.SimpleSignonServlet</servlet-class>
        </servlet>

        <servlet-mapping>
                <servlet-name>SimpleSignon</servlet-name>
                <url-pattern>/parameters</url-pattern>
                <url-pattern>/authenticate</url-pattern>
                <url-pattern>/sendtoken</url-pattern>
                <url-pattern>/logout</url-pattern>
        </servlet-mapping>
        
        <servlet>
                <servlet-name>KeyMaster</servlet-name>
                <servlet-class>com.caplin.signon.KeyMasterServlet</servlet-class>

                <init-param>
                        <param-name>caplin.keymaster.privatekey.filename</param-name>
                        <param-value>WEB-INF/private.pem</param-value>
                        <description>Name of the private key filename</description>
                </init-param>
        </servlet>
        
        <servlet-mapping>
                <servlet-name>KeyMaster</servlet-name>
                <url-pattern>/keymaster</url-pattern>
        </servlet-mapping>

        <filter>
                <filter-name>AuthenticationFilter</filter-name>
                <filter-class>com.caplin.signon.AuthenticationFilter</filter-class>
                <init-param>
                        <param-name>allowed.auth.levels</param-name>
                        <param-value>2FA</param-value>
                </init-param>
        </filter>

        <filter-mapping>
                <filter-name>AuthenticationFilter</filter-name>
                <url-pattern>/keymaster</url-pattern>
        </filter-mapping>        
</web-app>

The abstract signon servlet should be subclassed and the abstract methods: authenticate() and sendToken() implemented as required.

Example authenticate implementation:

public void authenticate(String scheme, String username, String password, String token, SessionData sessionData,
Map attributes, HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
        // check the user exists
        User user = users.get(username);
        if (user == null)
        {
                sendAuthenticateError(req, resp, "Invalid user");
                return;
        }
        
        String newLevel = null;
        String nextStep = null;
        
        // check 1AF
        if (scheme.equals(SessionData.SCHEME_USER))
        {
                if (password.equals(user.password))
                {
                        newLevel = SessionData.LEVEL_1FA;
                        nextStep = SessionData.LEVEL_2FA; // user must now authenticate using a 2FA scheme
                }
        }
        
        // check SMS 2FA
        else if (scheme.equals(SessionData.SCHEME_SMS) && scheme.equals(sessionData.getSentScheme()))
        {
                // must be authenticated to level 1FA already
                if (sessionData.getLevel().equals(SessionData.LEVEL_1FA))
                {
                        // check scheme and token are same as sent
                        if (token.equals(sessionData.getSentSchemeToken()))
                        {
                                newLevel = SessionData.LEVEL_2FA;
                        }
                }
        }
        
        // if the level has been updated
        if (newLevel != null)
        {
                // update the session with the new level and scheme
                sessionData.setUserName(username);
                sessionData.setLevel(newLevel);
                sessionData.setScheme(scheme);
                sendAuthenticateOK(req, resp, newLevel, nextStep);
        }
        else
        {
                sendAuthenticateError(req, resp, "Invalid signon");
        }
}

Example sendToken implementation:

public void sendToken(String scheme, String username, SessionData sessionData, Map attributes,
HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
        User user = users.get(username);
        if (user == null)
        {
                sendSendTokenError(req, resp, "Invalid user");
                return;
        }
        
        if (scheme.equals(SessionData.SCHEME_SMS))
        {
                String token = generateSMSToken();
                
                // try to send the token via SMS
                boolean sent = sendSMSToken(user.smsPhoneNumber, token);
                if (sent)
                {
                        // record scheme and token sent in the session
                        sessionData.setSentScheme(SessionData.SCHEME_SMS);
                        sessionData.setSentSchemeToken(token);
                        sendSendTokenOK(req, resp, "SMS token has been sent to " + user.smsPhoneNumber);
                }
                else
                {
                        sendSendTokenError(req, resp, "Error sending token using SMS");
                }
        }
        else
        {
                sendSendTokenError(req, resp, "Error invalid 2FA scheme");
        }
}

private boolean sendSMSToken(String phoneNumber, String text)
{
        // implement this to send SMS message
        
        return true;
}

private String generateSMSToken()
{
        // implement this to generate the SMS token
        
        return "123456";
}

Two examples are provided, the SimpleSignonServlet example demonstrates how to authenticate a user using SMS for the 2FA method, the EncryptedSignonServlet example shows how the password and SMS token could be encrypted by using a public/private key pair and a Javascript encryption library.

Packages 
Package Description
com.caplin.signon