Thursday, November 12, 2015

Adding New Challenge Question Sets in WSO2 Identity Server

WSO2 Identity Server supports recovering user accounts with challenge questions [1] (AKA security/secret questions). This blog post explains how to add new challenge questions or update existing challenge questions in Identity Server. For demonstration, I am using Identity Server 5.1.0 version. However the steps in this are same for Identity Server 5.0.0 version as well.

Challenge Questions can be added in following 3 ways which are explained in details below.

  1. From the UserIdentityManagementAdminService SOAP API
  2. By manually creating registry resources for questions
  3. From Identity Server Management Console

First we need to understand the structure of challenge questions. The challenge questions exist as groups (or sets) where each group has a unique ID which we call as questionSetId. One set can contain multiple challenge questions. By default there are two sets of challenge questions as listed below where each set has four questions.

City where you were born ?
Father's middle name ?
Favorite food ?
Favorite vacation location ?

Model of your first car ?
Name of the hospital where you were born ?
Name of your first pet ?
Favorite sport ?

We can get to know the existing challenge questions by calling the getAllChallengeQuestions method of UserIdentityManagementAdminService [2]. Here is a sample SOAP request for calling this method.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org">
  <soapenv:Header/>
  <soapenv:Body>
     <ser:getAllChallengeQuestions/>
  </soapenv:Body>
</soapenv:Envelope>

Here is the SOAP response where it gives details about the existing challenge questions.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
     <ns:getAllChallengeQuestionsResponse xmlns:ns="http://services.mgt.identity.carbon.wso2.org" xmlns:ax2322="http://dto.mgt.identity.carbon.wso2.org/xsd" xmlns:ax2320="http://mgt.identity.carbon.wso2.org/xsd">

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>City where you were born ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Father's middle name ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Favorite food ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Favorite vacation location ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Model of your first car ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Name of the hospital where you were born ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Name of your first pet ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Favorite sport ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

     </ns:getAllChallengeQuestionsResponse>
  </soapenv:Body>
</soapenv:Envelope>

For each set of questions, there should be a related claim inside the wso2 carbon claim dialect (http://wso2.org/claims). The claim Uri of this claim should be equal to the questionSetId of the questions set. That is because when we pick a challenge question and set to a user account, Identity Server needs to know where to store the question and answer for the user in the underlying userstore. That is stored in the Mapped Attribute defined in the claim.

For the default two sets of challenge questions, the claim configuration is as following. The mapped attribute names can be changed according to the underlying userstore of your setup.



So let’s see how to add a new set of challenge questions. First step is to add a new claim for this questions set in the wso2 carbon claim dialect. Login to the Identity Server Management Console and follow the steps below. (If you are using Identity Server 5.0.0, the User Interface will be different for adding a new claim. For that, refer [3]).


For the new claim, I am using the following values.

Dialect
Display Name
Challenge Question 3
Description
Challenge Question 3
Claim Uri
Mapped Attribute(s)
title

Here you can use any mapped attribute name that is supported by the underlying userstore. For demonstration purpose I am using the ‘title’ attribute which is supported in the default LDAP userstore of Identity Server.
After adding the new claim, it will appear as below.

Next step is adding the new challenge question set. For that, we can call the setChallengeQuestions method of UserIdentityManagementAdminService.

Here is the format of the SOAP request we need to send.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org" xmlns:xsd="http://dto.mgt.identity.carbon.wso2.org/xsd">
  <soapenv:Header/>
  <soapenv:Body>
     <ser:setChallengeQuestions>

        <!--One or more repetitions:-->

        <ser:challengeQuestionDTOs>
           <xsd:order>?</xsd:order>
           <xsd:promoteQuestion>?</xsd:promoteQuestion>
           <xsd:question>?</xsd:question>
           <xsd:questionSetId>?</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

     </ser:setChallengeQuestions>
  </soapenv:Body>
</soapenv:Envelope>

The important thing to note here is that this will overwrite the existing challenge questions. So if you need to keep the existing challenge question sets and add a new set, you need to include all the challenge questions in the request including the existing ones.

For demonstration purpose, I will create 3 challenge question sets where each set has 2 questions in it. Note that the questionSetIds have to be already added as claims which we did previously.  

Here is the sample SOAP request for creating the challenge questions.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.mgt.identity.carbon.wso2.org" xmlns:xsd="http://dto.mgt.identity.carbon.wso2.org/xsd">
  <soapenv:Header/>
  <soapenv:Body>
     <ser:setChallengeQuestions>
        <!--One or more repetitions:-->

        <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>City where you were born ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion1</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

       <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>Father's middle name ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion1</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

       <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>Model of your first car ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion2</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

       <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>Name of the hospital where you were born ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion2</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

       <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>Favourite movie ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion3</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

       <ser:challengeQuestionDTOs>
           <xsd:order>0</xsd:order>
           <xsd:promoteQuestion>false</xsd:promoteQuestion>
           <xsd:question>Favourite Book ?</xsd:question>
           <xsd:questionSetId>http://wso2.org/claims/challengeQuestion3</xsd:questionSetId>
        </ser:challengeQuestionDTOs>

     </ser:setChallengeQuestions>
  </soapenv:Body>
</soapenv:Envelope>

After successfully creating the challenge questions, we receive the following SOAP response.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
     <ns:setChallengeQuestionsResponse xmlns:ns="http://services.mgt.identity.carbon.wso2.org">
        <ns:return xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
     </ns:setChallengeQuestionsResponse>
  </soapenv:Body>
</soapenv:Envelope>

You can also verify that the challenge questions are correctly set by calling the getAllChallengeQuestions method in UserIdentityManagementAdminService again. This is SOAP the response I received when calling this method.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
     <ns:getAllChallengeQuestionsResponse xmlns:ns="http://services.mgt.identity.carbon.wso2.org" xmlns:ax2322="http://dto.mgt.identity.carbon.wso2.org/xsd" xmlns:ax2320="http://mgt.identity.carbon.wso2.org/xsd">

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>City where you were born ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Father's middle name ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion1</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Model of your first car ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Name of the hospital where you were born ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion2</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Favourite movie ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion3</ax2322:questionSetId>
        </ns:return>

        <ns:return xsi:type="ax2322:ChallengeQuestionDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
           <ax2322:order>0</ax2322:order>
           <ax2322:promoteQuestion>false</ax2322:promoteQuestion>
           <ax2322:question>Favourite Book ?</ax2322:question>
           <ax2322:questionSetId>http://wso2.org/claims/challengeQuestion3</ax2322:questionSetId>
        </ns:return>

     </ns:getAllChallengeQuestionsResponse>
  </soapenv:Body>
</soapenv:Envelope>

For associating these challenge questions with user accounts, you can follow [4].


If you use the Identity Server Dashboard [5] for associating challenge questions for user accounts, you can login to the Dashboard and go to Account Recovery -> View Details.




Then you will see all the available challenge question sets. In Identity Server 5.0.0 version Dashboard, it was only displaying two challenge question sets which was hard coded. Now with the 5.1.0 version it is improved such that dynamically it displays all the available challenge question sets.


So where does the Identity Server store these challenge questions ? It does not store them in the internal database, instead it stores the questions in the registry.


You can browse the registry from the Management Console and go to the following path.


/_system/config/repository/components/org.wso2.carbon.identity.mgt/questionCollection




If you click on a question (registry resource) and view the properties, you can see them as below.


Can we add a new Challenge Question by creating a registry resource without using the SOAP API ? Yes it is possible. For that, browse to the above mentioned registry path from the Management Console and click on ‘Add Resource’.



Select ‘Create Text Content’ as the method of adding the resource. For the name of the resource, you can type the next question number. Here in my case it is ‘question6’.
After adding the resource it will appear in the registry browser as below.
Once you add the resource, click on it to view the details of the resource. Here we need to add some properties to make this resource a challenge question. For that expand the ‘Properties’ section.
Using the ‘Add New Property’, you can add the required properties.


A property has a name and a value.
The required properties are ‘isPromoteQuestion’, ‘question’ and ‘questionSetId’. For the questionSetId, you need to add the claim URI of the question set so that this question will belong to that particular group. By default the registry resource will have a property named resource.source. You can delete this property. Once you set the 3 properties, it will look as below.


Now if you call the getAllChallengeQuestions method of UserIdentityManagementAdminService, you will get the newly added question also in the response.


If you go to the Identity Server Dashboard, the newly added question will be available under the particular question group.


You can also add new challenge question sets or modify existing questions from the Management Console itself. You can go to Configure -> Challenge Questions  (in Identity Server 5.1.0) and the existing challenge question sets will be then displayed.


If you are using Identity Server 5.0.0, you need to go to Configure -> Users and Roles -> Manage Challenge Questions -> Add Questions for this feature.

For adding a new challenge question set, you can click on the ‘Add new challenge questions set’ link.
You can type the Challenge Question Set Id and the question text. Note that if you are adding a new challenge question set ID, then there should be a claim with the same claim URI already added in the system. You can add multiple questions to the same set at once. After adding all the questions, click ‘Finish’.
After adding the question set successfully, you can view it.

You can also click on a particular challenge question set and modify the existing questions.
For associating these challenge questions with user accounts, a step by step guide is available in [4].

References

Tharindu Edirisinghe
Identity Server Team
WSO2

No comments:

Post a Comment