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-module
evaluates 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
file
login 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
, andkeystore
login 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 thekerberos5
andgss
login-module
elements, see Configuring Kerberos V5 Network Authentication. -
Custom login modules
If you use the
Application Token
authentication 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-modules
element within theauthentication
element that you started in Step 1. Thelogin-modules
element 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
file
type (to handle thejaas-config.xml
that is part of Kaazing Gateway) and theldap
type. 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-module
at 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
file
login module fails, then authentication processing does not progress down the chain. - If the
file
login module succeeds, then processing continues to the next login module in the example, which is theldap
login module.
- If the
- The
login-module
at the end of the chain verifies the information against a database, such as thejaas-config.xml
file or the LDAP server. Because theldap
login 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
success
element 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
success
element. In the example shown in the previous step, setting therequisite
property indicates that thefile
login module must succeed. See the table in the Notes section for more information about setting thesuccess
element. -
Ensure the corresponding
service
element’shttp-challenge-scheme
type aligns with the set of login modules in the Gateway configuration.For example, the
file
login module may require a username and password as part of its login needs. Thus, you should probably configure thebasic
authentication 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 acustom
login module), then configure thehttp-challenge-scheme
element forNegotiate
,Application Negotiate
, orApplication Token
.Note: If you use the
Application Token
scheme, 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