C# Example

C# Example

Centrify provides an example ASP.net application composed of a Visual Studio solution with two projects. The example shows a custom web application built using the Centrify REST API. The Centrify.Samples.AspNet.ApiDemo project contains the user interface code while the Centrify.Samples.AspNet.ApiDemo project contains implementations for common web operations (for example, a REST client that abstracts away the logic to construct and invoke a REST endpoint).

The code is available on GitHub and can be used as a starting point for creating your application.

This section provides a brief overview showing how the code performs authentication using the Centrify Identity Platform API's.

The authentication steps performed by the example application’s login screen are located in login.aspx.cs (Centrify.Samples.AspNet.ApiDemo project) which is responsible for the login screen UI. The following shows the login screen in its initial state:

1163

The StartAuthentication() method in login.aspx.cs is invoked when the user enters their name and clicks Next:

protected void StartAuthentication(object sender, EventArgs e)
{
    RestClient authenticationClient = UiDrivenLogin.Authenticate(TenantUrl, UserName_TextBox.Text + DefaultDomain);


    if (authenticationClient != null)
    {
        ProcessChallenges(authenticationClient.ChallengeCollection);
        Session["AuthenticaitonClient"] = authenticationClient;


        UserName_TextBox.Enabled = false;
        SocialLogin_Div.Visible = false;
        Register_HyperLink_Div.Visible = false;


        if (authenticationClient.AllowPasswordReset)
        {
            ForgotPass_Button.Visible = true;
        }


        RememberMe_Div.Visible = true;
        StartOver_Button.Visible = true;
    }
    else
    {
        //There was an error
        Login_Div.Visible = false;
        FailureText.Text = "There was an unexpected error. Please contact your system administrator. Click here to <a href=\"Login.aspx\">start over.</a>";
        ErrorMessage.Visible = true;
    }
}

This method obtains a RestClient object from the helper class and method :UiDrivenLogin.Authenticate.

protected void StartAuthentication(object sender, EventArgs e)
{
    RestClient authenticationClient = UiDrivenLogin.Authenticate(TenantUrl, UserName_TextBox.Text + DefaultDomain);

Note: The RestClient is stored in a Session hash table for use in subsequent endpoint calls.

UiDrivenLogin.Authenticate invokes the /Security/StartAuthentication endpoint using the TenantUrl, which the code obtains from the project’s AppSettings resource startup:

public partial class Login : System.Web.UI.Page
{
    public static string TenantUrl = ConfigurationManager.AppSettings["TenantUrl"].ToString();
    .
    .

UiDrivenLogin.Authenticate() also passes in the username that the user entered in the screen’s User Name field.

StartAuthetication() then invokes ProcessChallenges(), which iterates through the Challenges collections returned, and populates a list of mechanisms from which the user can select from. The code also displays UI elements for the default selected challenge:

protected void ProcessChallenges(dynamic challengeCollection, bool doSecondChallenge = false)
{
    //We should clear our dropdown list every time to ensure we are starting with a clean list.
    MFAChallenge_DropDownList.Items.Clear();


    // We need to satisfy one of each challenge collection:
    SortedList<string, string> items = new SortedList<string, string>();


    //If doSecondChallenge is true we will change the challenge set array to the second set. Otherwise, we will use the first Challenge set
    int challengeSet = 0;


    if (doSecondChallenge)
    {
        challengeSet = 1;
    }


    //The first step for the drop down list is to populate a SortedList with our MFA mechanisms
    for (int mechIdx = 0; mechIdx < challengeCollection[challengeSet]["Mechanisms"].Count; mechIdx++)
    {
        string mechValue = ConvertDicToString(challengeCollection[challengeSet]["Mechanisms"][mechIdx]);
        items.Add(UiDrivenLogin.MechToDescription(challengeCollection[challengeSet]["Mechanisms"][mechIdx]), mechValue);
    }


    //The second step for the drop down list is to bind the SortedList to our dropdown element
    try
    {
        MFAChallenge_DropDownList.DataTextField = "Key";
        MFAChallenge_DropDownList.DataValueField = "Value";
        MFAChallenge_DropDownList.DataSource = items;
        MFAChallenge_DropDownList.DataBind();


    }
    catch (Exception ex)
    {
        //There was an error
        Login_Div.Visible = false;
        FailureText.Text = ex.InnerException.ToString();
        ErrorMessage.Visible = true;
    }


    StartAuthentication_Next_Button.Visible = false;
    AdvanceAuthentication_Next_Button.Visible = true;
    Form.DefaultButton = AdvanceAuthentication_Next_Button.UniqueID;
    MFAChallenge_DropDownList.Visible = true;
    MFAChallenge_DropDownList_Label.Visible = true;


    Dictionary<string, string> mech = MFAChallenge_DropDownList.SelectedItem.Value.TrimEnd(';').Split(';').ToDictionary(item => item.Split('=')[0], item => item.Split('=')[1]);


    //If our first mechanism in our dropdown has a text answer type we should show our MFA answer text box.
    if (mech["AnswerType"] == "Text")
    {
        MFAAnswer_Label.Text = UiDrivenLogin.MechToPrompt(mech);
        MFAAnswer_Label.Visible = true;
        MFAAnswer_Textbox.Visible = true;
    }
    else if (mech["AnswerType"] != "Text" && mech["AnswerType"] != "StartTextOob")
    {
        //Set the Loading text for polling            
        MFALoad_Label.Text = MFAAnswer_Label.Text = UiDrivenLogin.MechToPrompt(mech);
    }
}

After this method completes, the screen now appears as follows:

1179

The user then enters the password and clicks Next. This invokes AdvanceAuthentication(), which obtains the selected mechanism (a Dictionary object called mech containing the challenge details) and the RestClient that was stored by StartAuthentication().

The code then invokes UiDrivenLogin.AdvanceForMech(), which invokes /Security/AdvanceAuthentication. The results from UiDrivenLogin.AdvanceForMech() are stored in resultsDictionary. The code displays a UI where the user enters the details required to complete the mechanism. The UI depends on the type of mechanism selected by the user.

//Called when the user selects an MFA type from the MFA DropDown and presses next.
protected void AdvanceAuthentication(object sender, EventArgs e)
{
    Dictionary<string, string> mech = MFAChallenge_DropDownList.SelectedItem.Value.TrimEnd(';').Split(';').ToDictionary(item => item.Split('=')[0], item => item.Split('=')[1]);
    RestClient authenticationClient = (RestClient)Session["AuthenticaitonClient"];


    Dictionary<string, dynamic> resultsDictionary = new Dictionary<string, dynamic>();


    if (sender.Equals(ForgotPass_Button))
    {
        resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, null, null, false, true);
        MFAAnswer_Label.Visible = false;
        MFAAnswer_Textbox.Visible = false;
        RememberMe_Div.Visible = false;
        ForgotPass_Button_Div.Visible = false;
    }
    else
    {
        //If the mechanism is StartTextOob we need to allow the user to answer via text and poll at the same time. 
        if (mech["AnswerType"] == "StartTextOob")
        {
            if (StartTextOob_Timer.Enabled) //We have already called AdvanceAuthentication once and are calling it again using the MFA Answer Text Box submit button.
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value, MFAAnswer_Textbox.Text, true);
            }
            else //This is the first time calling AdvanceAuthenticaiton and we need to start polling and turn on our TimerTick while also displaying the MFA Answer Text Box for user input.
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value);


                MFAAnswer_Label.Text = UiDrivenLogin.MechToPrompt(mech);
                MFAAnswer_Label.Visible = true;
                MFAAnswer_Textbox.Visible = true;
                StartTextOob_Timer.Enabled = true; //Used to poll again every tick while waiting for user input in the form.
            }
        }
        else
        {
            //User input is required for standard text mechanisms
            if (mech["AnswerType"] == "Text")
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value, MFAAnswer_Textbox.Text);
            }
            //No user input required for polling mechanisms
            else
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value);
                MFALoad_Label.Text = ""; //Change the loading text back to default
            }


            MFAAnswer_Label.Visible = false;
            MFAAnswer_Textbox.Visible = false;
        }
    }


    //Check results
    if (resultsDictionary["success"])
    {
        MFAChallenge_DropDownList.Visible = false;
        MFAChallenge_DropDownList_Label.Visible = false;


        //MFA was successful. We need to process the results to determine what should happen next.
        if (resultsDictionary["Result"]["Summary"] == "StartNextChallenge" || resultsDictionary["Result"]["Summary"] == "LoginSuccess" || resultsDictionary["Result"]["Summary"] == "NewPackage")
        {
            StartTextOob_Timer.Enabled = false; //Turn off timer so that we are not polling in the background for StartTextOob
            ProcessResults(resultsDictionary);
        }
        //If the summary is OobPending this means that StartTextOob has returned pending and we need to stop and wait. If any other results are present there is an error.
        else if (resultsDictionary["Result"]["Summary"] != "OobPending")
        {
            //There was an error
            Login_Div.Visible = false;
            FailureText.Text = "There was an unexpected error. Please contact your system administrator. Click here to <a href=\"Login.aspx\">start over.</a>";
            ErrorMessage.Visible = true;
        }
    }
    else
    {
        //There was an error
        Login_Div.Visible = false;
        FailureText.Text = resultsDictionary["Message"] + " Click here to <a href=\"Login.aspx\">start over.</a>";
        ErrorMessage.Visible = true;
    }
}

UiDrivenLogin.AdvanceForMech() also looks to see if the mechanism indicates StartTextOob (which the code must poll until the authentication result is returned):

protected void AdvanceAuthentication(object sender, EventArgs e)
{
    .
    .
    .


    }
    else
    {
        //If the mechanism is StartTextOob we need to allow the user to answer via text and poll at the same time. 
        if (mech["AnswerType"] == "StartTextOob")
        {
            if (StartTextOob_Timer.Enabled) //We have already called AdvanceAuthentication once and are calling it again using the MFA Answer Text Box submit button.
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value, MFAAnswer_Textbox.Text, true);
            }
            else //This is the first time calling AdvanceAuthenticaiton and we need to start polling and turn on our TimerTick while also displaying the MFA Answer Text Box for user input.
            {
                resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value);


                MFAAnswer_Label.Text = UiDrivenLogin.MechToPrompt(mech);
                MFAAnswer_Label.Visible = true;
                MFAAnswer_Textbox.Visible = true;
                StartTextOob_Timer.Enabled = true; //Used to poll again every tick while waiting for user input in the form.
            }
        }
.
.

If an OOB challenge is selected, the code starts a timer which repeatedly checks for a Summary value of StartNextChallenge, LoginSuccess, or NewPackage:

protected void StartTextOob_TimerTick(object sender, EventArgs e)
{
    //Start polling to see if user completed MFA remotely
    RestClient authenticationClient = (RestClient)Session["AuthenticaitonClient"];
    Dictionary<string, dynamic> resultsDictionary = UiDrivenLogin.AdvanceForMech(authenticationClient, authenticationClient.TenantId, authenticationClient.SessionId, RememberMe.Checked, null, MFAChallenge_DropDownList.SelectedItem.Value, "PollOnce", true);


    //MFA was successful. We need to process the results to determine what should happen next.
    if (resultsDictionary["Result"]["Summary"] == "StartNextChallenge" || resultsDictionary["Result"]["Summary"] == "LoginSuccess" || resultsDictionary["Result"]["Summary"] == "NewPackage")
    {
        StartTextOob_Timer.Enabled = false;
        ProcessResults(resultsDictionary);
    }
    //If there is an error we need to stop polling
    else if (!resultsDictionary["success"])
    {
        StartTextOob_Timer.Enabled = false;


        Login_Div.Visible = false;
        FailureText.Text = resultsDictionary["Message"] + " Click here to <a href=\"Login.aspx\">start over.</a>";
        ErrorMessage.Visible = true;
    }
}

If any of those three values are present, the code invokes ProcessResults(), passing the dictionary of results obtained from invoking UiDrivenLogin.AdvanceForMech():

protected void ProcessResults(Dictionary<string, dynamic> resultsDictionary)
{
    RestClient authenticationClient = (RestClient)Session["AuthenticaitonClient"];


    //Once one challenge list has been completed, start the next one.
    if (resultsDictionary["Result"]["Summary"] == "StartNextChallenge")
    {
        ProcessChallenges(authenticationClient.ChallengeCollection, true);
    }
    //If a new package was presented, start advance auth over again.
    else if (resultsDictionary["Result"]["Summary"] == "NewPackage")
    {
        authenticationClient.ChallengeCollection = resultsDictionary["Result"]["Challenges"];
        Session["AuthenticaitonClient"] = authenticationClient;
        ProcessChallenges(resultsDictionary["Result"]["Challenges"]);
    }
    //Successful Login
    else if (resultsDictionary["Result"]["Summary"] == "LoginSuccess")
    {
        //Login Complete
        Login_Div.Visible = false;
        Session["OTP"] = resultsDictionary["Result"]["Auth"];
        //Make sure our fully authenticated client is updated in the session.
        Session["AuthenticaitonClient"] = authenticationClient;
        Session["isLoggedIn"] = "true";


        UserManagement userManagementClient = new UserManagement(authenticationClient);


        //Check if user is admin
        Dictionary<string, dynamic> getUserInfo = userManagementClient.GetUserInfo();


        if (getUserInfo["Result"]["IsSysAdmin"])
        {
            Session["isAdmin"] = "true";
        }


        if (Request.QueryString["redirect"] != null)
        {
            Server.Transfer(Request.QueryString["redirect"], true);
        }
        else
        {
            Server.Transfer("Default.aspx", true);
        }
    }
}

ProcessResults() first obtains the RestClient object that was stored by StartAuthentication(). It then analyzes the Summary element in the results dictionary. If the value is StartNextChallenge or NewPackage, then the code invokes ProcessChallenges() to advance the authentication. If the value for Summary is LoginSuccess, the user has been authenticated. The code then redirects the user to a screen to perform other functionality offered by the client.