How to Configure Your Preferred User Stores to Authenticate Users using WSO2

Written by anuradhak | Published 2020/05/06
Tech Story Tags: wso2is | wso2 | userstore-preference | identity-server | authentication | iam | identity-and-access-management | coding

TLDR WSO2 Identity Server allows keeping multiple user stores for your system to store users. There should be one primary user store (mandatory) and any number of secondary user stores. If one user store fails to authenticate the user even if there is the username(i.e password is not matching), authentication will be executed in chained UserStore managers recursively. Users in the CUSTOMER user store can’t use services in travelocity.com.com services.via the TL;DR App

WSO2 Identity server allows keeping multiple user stores for your system to store users and their roles. There should be one primary user store (mandatory) and any number of secondary user stores(optional). When creating a secondary user store we can provide a user store domain name for the secondary user stores. “PRIMARY” is the user store domain of the primary user store.
Now think about a service provider who uses WSO2 Identity Server to
authenticate users who log into the application and it maintains multiple user stores in IS.
Now we are going to look at two different ways that users can provide their usernames during the login process:
1. The user provides the user name with the user store domain (eg: PRIMARY/peter). Then WSO2 IS validate username and password against the values stored in the PRIMARY user store.
2. The user provides only the username without specifying the user store domain (eg: peter). Then, WSO2 IS starts to authenticate the user starting from the PRIMARY user store. If one user store fails to authenticate the user even if there is the username(i.e password is not matching), authentication will be executed in chained UserStore managers recursively.
You can understand the second point from the highlighted lines in the
wso2/carbon-kernel AbstractUSerStoreManager.
        if (!authenticated && !domainProvided) {
            AbstractUserStoreManager userStoreManager;
            if (this instanceof IterativeUserStoreManager) {
                IterativeUserStoreManager iterativeUserStoreManager = (IterativeUserStoreManager) this;
                userStoreManager = iterativeUserStoreManager.nextUserStoreManager();
            } else {
                userStoreManager = (AbstractUserStoreManager) abstractUserStoreManager.getSecondaryUserStoreManager();
            }
            if (userStoreManager != null) {
                authenticated = userStoreManager.authenticate(userName, credential, domainProvided);
            }
        }
Let’s say the service provider now wants to narrow down the number of user stores that will be searched to authenticate a user when he/she doesn’t provide user store domain-specific username. How can we do this??
NOTE: This feature is available in IS-5.7.0 WUM updated version and IS-5.9.0 onwards
Let’s head-on with the following example to understand this process more clearly.
Identity Server — Product-IS 5.9.0 (download here)
Service provider — travelocity.com (deploy it according to the given instructions here)
Three Secondary JDBC user stores Named: “DRIVER”, “MANAGER”, “CUSTOMER” (instructions: here)
Add users according to the following table: (instructions: here)
Authenticating condition: Only the users in PRIMARY, DRIVER and MANAGER user store can access travelocity.com services. Users in the CUSTOMER user store can’t use services in travelocity.com.
Moreover, users don’t know their user store domain. Thus, they input username without the user store domain. (i.e username is just ‘peter’ not like ‘MANAGER/peter’).
Without any further configurations, try to login to travelocity.com app and
using four different username-password combinations in the table. You
will be succeeded.
Let’s narrow down the number of user stores allowed for travelocity.com login.
You need to do two things:
1. Implement UserStorePreferenceOrderSupplier ​ interface with your own logic to retrieve the allowed user stores.
public interface UserStorePreferenceOrderSupplier <T>{

    /**
     * Generate the user store order.
     * @return
     * @throws UserStoreException
     */
    T get() throws UserStoreException;
}
2. Then extend CallBackHandlerFactory ​ and create an object of your custom UserStorePreferenceOrderSupplier.
public class CallBackHandlerFactory {

    /**
     * Create user store preference order supplier.
     */
    public UserStorePreferenceOrderSupplier<List<String>> createUserStorePreferenceOrderSupplier
    (AuthenticationContext context, ServiceProvider serviceProvider) {

        return new DefaultUserStorePreferenceOrderSupplier(context, serviceProvider);
    }
}
This is the logic implemented according to the above requirement. (Find the sample here)
  1. CustomUserStoreOrderCallbackFactory class extends
    CallBackHandlerFactory
    .
  2. SimpleUserStoreOrderCallbackHandler implements
    UserStorePreferenceOrderSupplier<List<String>>
    .
  3. RegistryBasedUserStoreOrderCallbackHandler extends
    SimpleUserStoreOrderCallbackHandler
    .
  4. Logic: Users who are in PRIMARY, MANAGER, and DRIVER user stores can log in to travelocity.com service provider. This
    configuration is read from Registry.
NOTE: In the pom.xml file, you need to update the
org.wso2.carbon.identity.framework
dependency version according to the
IS pack you are using.
        <dependency>
            <groupId>org.wso2.carbon.identity.framework</groupId>
            <artifactId>org.wso2.carbon.identity.application.authentication.framework</artifactId>
            <version>5.14.97</version>
        </dependency>
Also, Make sure the carbon-kernal which is used in the IS pack lies in the defined range.
<carbon.kernel.package.import.version.range>[4.4.0, 5.0.0)</carbon.kernel.package.import.version.range>

NOW what should we do ??

1. Build the repository which contains the login using
mvn clean install
command.
2. Inside target folder, you will find org.wso2.carbon.identity.custom.callback.userstore-1.0-SNAPSHOT.jar
3. Copy and paste the generated jar file
“​
org.wso2.carbon.identity.custom.callback.userstore-1.0-SNAPSHOT.jar

into​ <IS-HOME>/repository/components/dropins/
4. Configure the extended CallBackHandlerFactory in
<IS-HOME>/repository/conf/identity/application-authentication.xml file, under
<ApplicationAuthentication xmlns="http://wso2.org/projects/carbon/application-authentication.xml">
<Extensions>
...
<CallbackFactory>org.wso2.carbon.identity.custom.callback.userstore.CustomUserStoreOrderCallbackFactory</CallbackFactory>
…
</Extensions>
NOTE: From IS-5.10.0 onwards this modification should be done through
<IS-HOME>/repository/conf/deployment.toml
file. Add the following lines to deployment.toml.
[authentication.framework.extensions] 
callback_factory = "org.wso2.carbon.identity.custom.callback.userstore.CustomUserStoreOrderCallbackFactory"
5. Start the server by issuing
 ./wso2server.sh 
(Linux)/
./wso2server.bat 
(Windows) on terminal navigating to
<IS-HOME>/bin
6. Navigate
Main -> Registry -> Browse
​ and ​ click on
Browse
.
7. Navigate to
_system -> config
. You will find a file named
userstore-metadata.xml
(This file is defined in the
CustomCallbackUserstoreServiceComponent
class as
REG_PATH
) Click on
userstore-metadata.xml
.
8. Click
+
button in
Properties
tab. It will appear as follows. These values will be used when authenticating users.
Now try to log into the travelocity.com using the username-password pairs we introduced at the beginning.
The result:
  • Users in PRIMARY/ MANAGER/ DRIVER user stores will log into the system.
  • Users in the CUSTOMER user store will fail to login.
We are done!! Don’t forget to applause if you get something by this blog.

Published by HackerNoon on 2020/05/06