How-To: Enable Multiple Mutable Login Identifiers in WSO2 Identity Server

Separating the Singular Immutable Identifier from Multiple Mutable Identifiers

Johann Dilantha Nallathamby
Identity Beyond Borders
6 min readAug 2, 2019

--

Problems

Three separate but related, serious limitations that crop up, individually or often together with WSO2 Identity Server (IS) are:

  1. No singular persistent pseudonym for a user
  2. The inability to modify the login identifier of a user
  3. The inability to use multiple login identifiers for a user

Although these three limitations can be viewed separate from one another, they are related because, they all are due to the same design choice in the product, which continues to be unchanged even in the latest version of the product, at the time of writing this post — 5.8.0.

Cause

The singular persistent pseudonym (user ID) of a user is also used as the login identifier (login ID). In other words the user ID and login ID cannot be separated.

Description

One of the widely accepted IAM design principles, is to have a singular persistent pseudonym for each user [1]. This pseudonym must be unique and immutable. This pseudonym is what is used to maintain references to data that belong to a particular user across various data stores such databases, analytics, log files, etc. The pseudonym is generally opaque and pseudo-random, i.e. in machine readable format, and has no discernible correspondence with meaningful global identifiers such as e-mail addresses or mobile numbers. This feature abides by the privacy-by-design principle [8,9]. This pseudonym is generally referred to as “user ID” in most systems.

(In WSO2 context the “user ID” is sometimes also referred to as “username” mostly because, the fields that reference the user in most of the WSO2 Identity Server schema tables are named “USERNAME”).

There can be multiple mutable login identifiers for each user that are accepted by the system. These login identifiers must be unique and mutable. While these login identifiers are used for login and/or display purposes, they are not used to maintain references because they are deemed to change. In addition privacy regulations such as GDPR strictly require global identifiers such as email addresses and mobile numbers to be modifiable for compliance. Login identifiers are generally in human-readable format to improve user experience. Global identifiers such as email addresses and mobile numbers are commonly used as login identifiers. The mapping between login identifiers and persistent identifiers must be managed separately in the system.

Limitations with WSO2’s Official Solution for LDAP User Stores

Although is a solution [2] that is provided for the multiple login identifiers problem specifically in a LDAP user store, in the official IS 5.8.0 documentation, this solution also has limitations.

For example, even though the LDAP UserStoreManager can be configured with search filters and list filters that contain more than one attribute as suggested in [2], there are event handlers[3] that get executed before and after the call to the UserStoreManager. For example, account locking on max incorrect password attempts” feature is implemented using an event handler subscribed to the PRE_AUTHENTCATE event. These handlers take the “user id” in one of their arguments, and they are implemented to work only with the “user id” and not with any other identifier. In the case of using multiple login identifiers with LDAP user stores, though the authentication may succeed at the UserStoreManager level, the identifier that gets passed to the event handler is the “login identifier” and not the “user id”. This can create two kinds of problems:

  1. If multiple login identifiers are used for the same user, the handler cannot correlate the identifiers that belong to the same user.
  2. Even in the case of using a single login identifier, if we consider that this “login identifier” value can change over time, then the state that is managed by the event handlers become stale and its function will produce incorrect results.

Solution

As explained above it is required to have a persistent pseudonym for the users. In WSO2 world the “user id” (sometimes referred to as “username”) is meant for this purpose. It is also treated as a special attribute of the user in the WSO2 Identity Server product APIs as well as in the database schema. Therefore we will continue to use this attribute as the persistent pseudonym.

The first problem we need to address is the separation of login identifier from user identifier. The best way to manage the login identifier is using a user claim which translates to a user attribute in the user store. So there are two requirements from any solution that we come up with:

  1. Authenticate with any other attribute of the user, other than the “user id”.
  2. Carry forward the “user id” in the rest of the flow, irrespective of the “login id” that was used. This will make sure that all other features which are implemented to work with “user id” continue to work as they were meant to work.

With the above two requirements in mind, we could think of two high level solutions as follows:

  1. Extend the appropriate UserStoreManager’s authenticate() method implementation to authenticate using user attributes [4]. However, this cannot be done as it is because the return type of authenticate() is void. Therefore we have no way of telling the caller what is the “user id” of the authenticated user. One way of overcoming that limitation would be to add an overloaded authenticate() method with an additional argument to specify the claim URI to search for (this could have also been included in the credential<Object> if we didn’t have to overload the method), and String return type which always returns the “user id” of the authenticated user. And then extend the points in the product where this API is being invoked, to carry forward the “user id”.
  2. Extend the points in the product where the UserStoreManager’s authenticate() API [4] is invoked and implement a logic, where it first does a search for the user using the login identifier, using the UserStoreManager’s getUserList() [5] API, and then uses the “user id” that was returned from the search to continue with the authentication and the rest of the flow.

Though there are a few places in the product that uses the UserStoreManager’s authenticate() API, the two most relevant places where this issue becomes a real showstopper are:

  1. BasicAuthenticator in the authentication and single sign-on framework [6]
  2. PasswordGrantHandler in OAuth2 [7]

In all other places such as management console login, Rest API access control valve, UsernameToken callback handler, etc. we don’t have actual end users authenticating. Therefore we can create some dedicated accounts for their use, while making sure these user identifiers which also serve as login identifiers, are unique and never going to change.

The second problem to address is supporting multiple login identifiers. As we used claims to solve the singular login identifier problem, naturally we can extend it to solve multiple login identifiers problem by using additional claims. However, the limitation we have with both suggested solutions above using claims is, the APIs in the UserStoreManager expect a claim URI. However, when we allow to authenticate using multiple login identifiers, we won’t know exactly which claim the user has used for a given authentication instance. If we try authenticating with all the attributes of the user, then the performance drops badly. However, this problem only applies to RDBMS user stores because, for LDAP user stores you can configure the standard filters to query with multiple attributes as mentioned in [2]. However, the JDBC SQL queries are not implemented to be queried with multiple attributes in the ‘WHERE’ clause. There are two ways to overcome this problem for RDBMS user stores in increasing order of complexity and cost of performance.

Method 1

  1. Either ask user to provide the login identifier type using an option button, or use a pattern matching technique in the login form to detect the login identifier type the user has entered before submitting the login form to IS. For example, checking the ‘@’ followed by hierarchical domains for email addresses, checking for numbers for mobile numbers, etc.
  2. Add the login id type as an additional parameter that gets submitted to IS.
  3. In the extensions discussed above decide on the claim URI based on the login identifier type that is submitted in (2).

Method 2

  1. Configure the attributes that can possibly be used as login identifiers, as a property of the user store.
  2. Pass “null” as the claim URI argument when doing the user search.
  3. Extend the JDBCUserStoreManager to read the custom property and try searching the user with each of the attributes specified in the custom property if the claim URI is null.

References

[1] https://wso2.com/whitepapers/identity-architect-ground-rules-ten-iam-design-principles/#02

[2] https://docs.wso2.com/display/IS580/Managing+User+Attributes#ManagingUserAttributes-Authenticationusingmultipleattributes

[3] https://medium.com/@isurakarunaratne/wso2-identity-server-eventing-framework-32505bcc1600

[4] https://github.com/wso2/carbon-kernel/blob/4.4.x/core/org.wso2.carbon.user.api/src/main/java/org/wso2/carbon/user/api/UserStoreManager.java#L41

[5] https://github.com/wso2/carbon-kernel/blob/4.4.x/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/UserStoreManager.java#L318

[6] https://github.com/wso2-extensions/identity-local-auth-basicauth/blob/master/components/org.wso2.carbon.identity.application.authenticator.basicauth/src/main/java/org/wso2/carbon/identity/application/authenticator/basicauth/BasicAuthenticator.java#L333

[7] https://github.com/wso2-extensions/identity-inbound-auth-oauth/blob/master/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/PasswordGrantHandler.java#L128

[8] https://wso2.com/articles/2019/05/privacy-by-design-as-a-system-design-strategy/

[9] https://wso2.com/library/article/2018/2/identity-and-access-management-to-the-rescue/

--

--