Configure a Chain of Login Modules
You configure one or more login modules, called a chain of login modules, to instruct the Gateway on how to validate user credentials with a user database and to determine a set of authorized roles. The chain of login modules decode and verify the credentials, and (along with the challenge handler) handle the challenge/response authentication sequence of events.
Before You Begin
This procedure is part of Configure Authentication and Authorization:
- Configure the HTTP Challenge Scheme
- Configure a Chain of Login Modules
- Configure a Challenge Handler on the Client
- Configure Authorization
To Configure a Chain of Login Modules
-
Determine the type of login modules you want to define.
A
login-moduleevaluates the encoded login credentials that the Gateway passes to it. The Gateway supports the following types of login modules:-
Built-in login module provided by Kaazing Gateway
The Gateway provides the
filelogin module for easy-to-implement security that is integrated with the Gateway. -
Standard (public) JDK-provided login modules
The Gateway supports
ldap,kerberos5,gss,jndi, andkeystorelogin modules, which are some of the most commonly used login modules for authentication and authorization purposes. In these implementations, you do not need to write your own login module solution. For information about using thekerberos5andgsslogin-moduleelements, see Configuring Kerberos V5 Network Authentication. -
Custom login modules
If you use the
Application Tokenauthentication scheme, you must supply your own custom login module. See Create a Custom Login Module and Integrate an Existing Custom Login Module into the Gateway.
-
- Add the
login-moduleselement within theauthenticationelement that you started in Step 1. Thelogin-moduleselement is the container for one or more login modules and it defines the scope in which security policies are enforced. -
Define one or more login modules to make a chain.
Each login module in the chain is responsible for doing a little piece of work and passing along information. For example, one login module might check a database, another login module might contact an LDAP directory, and so on.
In the following example, the chain of login modules includes the
filetype (to handle thejaas-config.xmlthat is part of Kaazing Gateway) and theldaptype. For a complete security example, see the Notes section below.<login-modules> <login-module> <type>file</type> <success>requisite</success> <options> <filename>jaas-config.xml</filename> </options> <login-module> <login-module> <type>ldap</type> <success>required</success> <options> <userProvider>ldap://ldap-svr:389/ou=people,dc=example,dc=com</userProvider> <userFilter> <![CDATA[(&(userPrincipalName={USERNAME}@MYCOMPANY.NET) (objectClass=inetOrgPerson))]]> </userFilter> <authzIdentity>{EMPLOYEENUMBER}</authzIdentity> </options> </login-module> </login-modules>In the example:
- The
login-moduleat the start of the chain often asks the Gateway for more information, via a callback into the Gateway (for example, using a token).- If the
filelogin module fails, then authentication processing does not progress down the chain. - If the
filelogin module succeeds, then processing continues to the next login module in the example, which is theldaplogin module.
- If the
- The
login-moduleat the end of the chain verifies the information against a database, such as thejaas-config.xmlfile or the LDAP server. Because theldaplogin module is the last one in the chain, the user authenticates successfully and the Gateway establishes the client’s list of authorized roles and permits the WebSocket connection to proceed (101 Protocol Upgrade). Otherwise, it denies the WebSocket creation request.
- The
-
Configure a
successelement for each login module.If there is more than one login-module configured in a realm, then configure the semantics and processing order for a chain of login modules using the
successelement. In the example shown in the previous step, setting therequisiteproperty indicates that thefilelogin module must succeed. See the table in the Notes section for more information about setting thesuccesselement. -
Ensure the corresponding
serviceelement’shttp-challenge-schemetype aligns with the set of login modules in the Gateway configuration.For example, the
filelogin module may require a username and password as part of its login needs. Thus, you should probably configure thebasicauthentication scheme that is expecting the user to login with a username and password. If you configure the chain of login modules to expect only tokens (such as with acustomlogin module), then configure thehttp-challenge-schemeelement forNegotiate,Application Negotiate, orApplication Token.Note: If you use the
Application Tokenscheme, then you must write your own custom login module. - Save the Gateway configuration.
Notes
The success element controls the behavior of the individual login modules, but the order of the login modules controls the overall behavior as the process of authentication walks down the stack. When there is more than one login-module configured in a realm you should carefully plan the order in which the login modules appear in the Gateway configuration because the login modules are invoked in sequence.
The following table describes how the order of login modules and the setting of the success element controls authentication processing:
success values |
Description | On success or failure … |
|---|---|---|
required |
The login-module must succeed. |
If it succeeds or fails, then authentication continues to proceed down the login-module list. |
requisite |
The login-module must succeed. |
If it succeeds, then authentication continues down the login-module list. If it fails, then authentication stops its process down the login-module list and the Gateway denies the WebSocket creation request. |
sufficient |
The login-module is not required to succeed. |
If it succeeds, then authentication stops its process down the login-module list and the Gateway opens the WebSocket connection. If it fails, authentication continues down the login-module list. |
optional |
The login-module is not required to succeed. |
If it succeeds or fails, then authentication proceeds down the login-module list. |
In addition:
- If a sufficient login module is configured and succeeds, then only the required and requisite login modules prior to that sufficient login module need to have succeeded for the overall authentication to succeed.
- If no required or requisite login modules are configured for an application, then at least one sufficient or optional login module must succeed.
The following code shows a complete security example:
<security>
<keystore>
<type>JCEKS</type>
<file>keystore.db</file>
<password-file>keystore.pw</password-file>
</keystore>
<truststore>
<file>truststore.db</file>
</truststore>
<realm>
<name>demo</name>
<authentication>
<http-challenge-scheme>Basic</http-challenge-scheme>
<http-header>X-Custom-Authorization-Header</http-header>
<http-query-parameter>myCustomAuthParam</http-query-parameter>
<http-cookie>sampleCookie1</http-cookie>
<login-modules>
<login-module>
<type>file</type>
<success>required</success>
<options>
<filename>jaas-config.xml</filename>
</options>
<login-module>
<login-module>
<type>ldap</type>
<success>required</success>
<options>
<userProvider>ldap://ldap-svr:389/ou=people,dc=example,dc=com</userProvider>
<userFilter>
<![CDATA[(&(userPrincipalName={USERNAME}@MYCOMPANY.NET)
(objectClass=inetOrgPerson))]]>
</userFilter>
<authzIdentity>{EMPLOYEENUMBER}</authzIdentity>
</options>
</login-module>
</login-modules>
</authentication>
</realm>
</security>
Sharing Information Among LoginModules in a Gateway and a Cluster 
You can share data among login modules in a login module chain using the sharedState object that is passed into the initialize() method. When you need to share data among login modules in a larger scope, such as among invocations of login module chains or even among different Gateway instances in a cluster, you can use an object accessible from within your login modules named ExpiringState (see org.kaazing.gateway.server.ExpiringState in the Kaazing WebSocket Gateway Server API).
The most typical use case for ExpiringState is to prevent authorization tokens from being reused. When reusing tokens is attempted by a malicious user, it is referred to as a token replay attack. The safest practice for tokens is to make them uniquely identifiable by including a nonce. This way, anything authorizing the token can reject it if the nonce has been used previously.
While validating a token, your login module can check ExpiringState to see whether the token nonce has been used. If so, then the login module can deny authorization. If not, then the login module can add the token nonce to ExpiringState for future login module invocations to check. Since ExpiringState is shared among Gateways in the cluster, it won't matter if the token replay attack occurs against a different Gateway instance.
Here is a sample from a LoginModule that uses ExpiringState:
public class TokenLoginModule implements LoginModule {
private ExpiringState expiringState;
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
...
this.expiringState = LoginModuleOptions.EXPIRING_STATE.get(options);
// Use the following instead in versions prior to 5.7:
// expiringState = (ExpiringState) options.get("ExpiringState");
...
}
public boolean login() throws LoginException {
...
processToken(tokenData);
...
}
private void processToken(String tokenData) throws FailedLoginException {
...
// Extract the nonce from the token.
String nonce = ...;
// Extract when the token expires from the token.
String expires = ...;
// Validate that the token hasn't expired
ZonedDateTime expiresDateTime = ZonedDateTime.parse(expires);
ZonedDateTime now = ZonedDateTime.now(expiresDateTime.getZone());
long expiresDuration = Duration.between(now, expiresDateTime).toMillis();
if ( expiresDuration <= 0 ) {
throw new FailedLoginException("Token has expired");
}
// Validate that the token hasn't been used already. If not, then store it until it expires
if ( expiringState.putIfAbsent(nonce, nonce, expiresDuration, TimeUnit.MILLISECONDS) != null ) {
throw new FailedLoginException("Token nonce has already been used");
}
...
Notes:
- To enable this Early Access feature, use the variable
login.module.expiring.state. - You can also share LoginModule state within a single LoginModule chain as described in the Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide.
Respond to Challenges on HTTP 401 Status Code 
In response to HTTP 401 codes, you can provide credentials via a call to the Java Authenticator set on the Java Virtual Machine (JVM). For example, you can use the Kaazing Embedded Gateway API to call Authenticator.setDefault.
The new connect-options property http.max.authentication.attempts must be set in order for the Gateway to respond to 401 codes. The http.max.authentication.attempts must be set with an integer value specifying how many times an HTTP connector will attempt to authenticate when it is challenged. The default value is 0.
To enable this Early Access feature, use the variable http.authenticator.
Next Steps
Configure a Challenge Handler on the Client