Wednesday, December 9, 2015

An Alternative Way for Diaplaying Tenant Domains Dropdown in SSO Login Page Using a Custom Authenticator

In WSO2 Identity Server, the Tenant Domains List Dropdown feature [2] is useful when the users may login to different tenants for authentication in a scenario where a SAAS is enabled in the Service Provider.


However in order to enable this feature, we need to set many configuration changes and also enable Mutual SSL authenticator in the Identity Server. If the environment is clustered having multiple Identity Server nodes, then we need to add several other configurations also to communicate between the servers to keep the active tenant domains list consistent among the nodes. When the cluster is fronted by a Load Balancer like Amazon ELB, there can be problem due to not supporting certificate based authentication (Mutual SSL).

However instead of using this feature, we can get the same output by writing a custom authenticator where inside the authenticator’s code, it can get the list of active tenants and send the list to the login page of the authentication endpoint webapp. Then in the login page, it can extract the tenant list and display in a dropdown. From the authenticator to the authentication endpoint webapp, the tenant list will be sent through a query parameter. When there are many tenants created, this will not be a good solution as the URL size increases.

However this solution would be useful in a scenario like below.

  1. The server is configured with multi step authentication.
  2. Assume the authentication Step 1 is a federated authenticator.
  3. User first logs into the federated authenticator.
  4. Then Identity Server receives claims from the federated authenticator in authentication Step 1.
  5. Based on some claim, inside the authentication Step 2 which is the custom authenticator (local), we can take a decision on which tenants to display. This can be based on the roles that the user (logged into Step 1) has.
  6. In such case, we can display a subset of all the active tenants where the user has some association with.

In a scenario like above, instead of using the Tenant Domains List Dropdown feature which Identity Server provides, we can implement our own custom authenticator and get our requirements satisfied.

In this blog post, I am providing a sample maven project and demonstrate how to get the tenant list displayed in the login page of authentication endpoint webapp.

For the demonstration I am using the Identity Server 5.0.0 version with Service Pack 1 installed.

First you need to clone the Github repository [2] which has the required resources. The maven project of the custom authenticator is the directory with name MyCustomAuthenticator [3]. You can build this project and get the jar file of the autheticator. Or else you can use the pre-built org.wso2.carbon.identity.application.authenticator.mycustomauthenticator-4.2.2.jar file available in the target directory of the project.

Copy the org.wso2.carbon.identity.application.authenticator.mycustomauthenticator-4.2.2.jar file into the IS_HOME/repository/components/dropins directory. (This is because the authenticator is written as an OSGI bundle).

Next step is to copy the two files in “AuthenticationEndpoint Webapp Changes” [4] directory to IS_HOME/repository/deployment/server/webapps/authenticationendpoint/ directory. Here you will be replacing the login.jsp file which is already existing. The customauth.jsp file is a new file which has the UI related to the authenticator. The only change in the login.jsp is that it checks the name of the authenticator and if it is our custom authenticator, then it includes the customauth.jsp file to be displayed.

Now we have done all the changes. Restart the Identity Server.

Then we need to setup a client application which uses Identity Server for authentication. For that, I am setting up the travelocity sample webapp following the steps described in [5].

In the Service Provider, I have enabled ‘Saas Application’ so that users from any tenant can authenticate with this Service Provider.

If you login to the travelocity.com sample application, you will see the usual login page because by default, the Basic Authenticator is used for authentication in the Service Provider.


Now we need to engage our custom authenticator with the Service Provider. Edit the Service Provider configuration and expand the ‘Local & Outbound Authentication Configuration’ section.


In the dropdown associated with the Local Authentication, it should list our custom authenticator with the name ‘custom’. Select the radio button ‘Local Authentication’ and from the authenticators dropdown, select the custom authenticator. Then save the Service Provider configuration. (If you want to use this for a scenario like multi step authentication, then you need to select ‘Advanced Configuration’ and setup this authenticator in a particular step).


Now try to login again to the travelocity.com sample. Then if you do not have any tenants created in the Identity Server, or if you do not have any active tenant (other than super tenant), you will see the below login page. This is loaded from the customauth.jsp file we added to authenticationendpoint webapp previously. You can change the styles as you wish.

If you have active tenants, then those will be displayed in a dropdown in the login page. By default there will be ‘Super Tenant’ in the list which is added from the customauth.jsp.

You can select a particular tenant from the dropdown.


As the username, you do not need to append the tenant domain now. Just type the username (without @tenantdomain), enter the password and you should be able to login to the travelocity sample app.

This way you can get the same functionality as Tenant Domains List Dropdown feature without any configuration other than Service Provider’s configuration.

If we talk about the technical aspects of this, inside the MyCustomAuthenticato.java [6] it has initiateAuthenticationRequest method where it retrieves the list of active tenants and sends to the authenticationendpoint as a query parameter with the name tenantList. The value is a comma separated string with all active tenant domains. Then inside the authenticationendpoint webapp, in customauth.jsp file, it retrieves this query parameter, split the string with “,” delimiter and load the tenant domain names to a dropdown in the UI.

So, in a usecase like multi step authentication which I mentioned previously, what you can do is, after the Step1 (federated authenticator) authentication, you can retrieve claims from the IDP. For example you may retrieve a role claim, and then inside this custom authenticator which is the Step2 of our authentication sequence in that usecase, you can access that particular claim received in previous step and take a decision. An example is based on the roles the user is granted, select a subset of tenants which are related to the user. And then send the list to the authenticationendpoint so that this user will not see all available tenants, instead the user will see a the particular set of tenants which the user is related to.

There can be many usecases where this kind of an implementation would be useful. You can refer to the code and modify it according to your requirements.

References :


Tharindu Edirisinghe
Identity Server Team
WSO2

9 comments:

  1. Hi Tharindu,
    I have looked into the code of customauthenticator . I could see that you are using authentication framework version 4.2.0 so that has AuthenticationContext.getSubject(String:) Method but latest WSO M6 or Beta versions are using authentication framework 4.5.4 plugins. Once I integrated your changes am landed on customlogin page which displays all the active domains then I have given the credentials. The problem here is after clicking on login it uses authentication framework jar from WSO plugins and fails with NosuchMethod error for AuthenticationContext.getSubject(String:) because in 4.5.4 signature has been modified. I tried to make changes to include 4.5.4jar in your customauthenticator code instead of 4.2.0 but 4.5.4 is not available in any of the online repository. Could you suggest any approach?

    Thanks,
    Prabu



    (https://github.com/thariyarox/TenantDomainsDropdownWithCustomAuthenticator/tree/master/MyCustomAuthenticator/src/main/java/org/wso2/carbon/identity/application/authenticator/mycustomauthenticator)

    ReplyDelete
  2. Hi Prabu,

    This sample is for Identity Server 5.0.0. However regarding your question, I will check how to modify the dependencies to match the jars packed with beta versions and let you know my suggestions.

    Thanks,
    Tharindu

    ReplyDelete
    Replies
    1. Sure Thanks. That would be great.

      Regards,
      Prabu

      Delete
  3. Hi Tharindu,
    I am able to resolve the above issue using below changes.
    1. Added 4.5.4 dependency in pom.xml instead of 4.2.0
    2. Instead of context.setSubject(username); in line no 278.
    //Replaced below
    AuthenticatedUser authenticatedUser = AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(username);
    context.setSubject(authenticatedUser);
    Now am able to login through 5.1 WSO IS except the below exception while serializing sessiondata which am trying to fix(Do you have any idea?)

    IMP:- I would like to know if it is possible to login using only emailaddress/pwd as credential instead of selecting tennant from the dropdown. WSO bydefault doesn't support this. Is there any service which gives me tenant domain by email address? If you have written or came across any shared code. Could you please share the github location to me?

    Exception Stacktrace caused by customauthcode:-
    ------------------------
    [2015-12-15 15:46:00,930] INFO {org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager} - LDAP connection created successfully in read-write mode
    [2015-12-15 15:46:00,981] ERROR {org.wso2.carbon.identity.application.authentication.framework.store.SessionDataStore} - Error while storing session data
    java.io.NotSerializableException: org.wso2.carbon.user.core.tenant.CommonHybridLDAPTenantManager
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    ...

    ReplyDelete
  4. Hi Prabu,

    In the login page, what it does is simply appending the tenant domain value from the dropdown to the value entered in the username textbox.

    eg : tenant domain dropdown has wso2.com as the value. Then the user types 'sam' as the username. Upon clicking on the login button, it runs the javascript function in [1], prepare the complete username '[email protected]' and write it to a hidden field.

    So inside the authenticator, as the username it will receive the fully qualified username.

    [1] https://github.com/thariyarox/TenantDomainsDropdownWithCustomAuthenticator/blob/master/AuthenticationEndpoint%20Webapp%20Changes/customauth.jsp

    ReplyDelete
    Replies
    1. Hi Tharindu,
      Thanks for the response. I understand that. Considering the multitenant environment. If the tenant wso2.com has the user email as [email protected]. Provided some config changes WSO allows him to login as [email protected]@wso2.com/password. But it will be difficult for the end user. So I want to know if we can login just using [email protected]/password instead of appending @wso2.com along with email.
      This can be achieved if WSO exposed any service to find domain by email address. But I couldn't find any service which meets the expectation. Do you have any idea?

      Thanks,
      Prabu

      Delete
  5. Hi Prabu,

    Currently we cannot serarch users across tenants. We can use RemoteUserStoreManagerService [1], it has the method getUserList where we can give a claim (i.e email address) and get the matching users. However this is an admin service and we need to provide the credentials of an admin user of a particular tenant. Then the search will happen only within the particular tenant where the admin belongs to. However with some custom implementation this functionality can be achieved. For example, if your underlying userstore is JDBC, then all the users are stored in UM_USER table [2]. You can write a custom component which exposes an API that searches in the entire UM_USER table when we provide a username or email. Similarly if the underlying userstore is LDAP, you can implement the logic such that it goes through the entire LDAP directory structure (in usersearch base of every tenant) and returns the matching user records.

    Be default we have not exposed such a service based on the Multitenantcy concepts.

    "Multitenancy is a reference to the mode of operation of software where multiple independent instances of one or multiple applications operate in a shared environment. The instances (tenants) are logically isolated, but physically integrated" [3]

    However with the custom implementation I mentioned, you should be able to achieve this functionality.

    [1] https://localhost:9443/services/RemoteUserStoreManagerService?wsdl
    [2] http://tharindue.blogspot.com/2015/04/wso2-identity-server-data-dictionary.html
    [3] http://www.gartner.com/it-glossary/multitenancy

    Regards,
    Tharindu

    ReplyDelete
  6. Thanks Tharindu. We are using LDAP (ReadWriteLDAPUserStoreManager). Do you have any sample code to search across directory structure by email or some other attribute? Could you share me the link if you have any.

    Thanks,
    Prabu

    ReplyDelete
  7. Hi Prabu,

    Currently we do not have a sample for the exact same thing, but we do have the code to access LDAP and search users so you can refer that and modify such that it iterates on different tenants (directory structures). If you can send an email to dev[at]wso2[dot]org , WSO2 developers will be able to help you with this.

    Thanks,
    Tharindu

    ReplyDelete