ASP.NET

Duo Universal C# ASP NET Web Forms

Duo recently released their new Universal two factor authentication method which changes from an iFrame method to using a redirect. Duo provides a C# example using .NET Core and MVC but if you still have old web forms applications you’ve built and aren’t ready, you can still integrate them with Duo Universal.

First, you’ll need to buil the DuoUniversal.dll binary from the project https://github.com/duosecurity/duo_universal_csharp and add it as a reference to your project. You’ll also need to make sure you’re running .NET Framework 4.7.1 or higher for your project.

Second, add the following items from Nuget to the project

  • System.Text.Json
  • System.Net.Http.Json
  • Microsoft.IdentityModel.Tokens
  • Microsoft.IdentityModel.JsonWebTokens

Now you’re ready to start integrating it into your current project.

  1. On your login page, add Using DuoUniversal
  2. Declare string values for Duo Settings
string ClientId = ""; //Setting from Duo
string ClientSecret = ""; //Setting from Duo
string ApiHost = ""; //Setting from Duo
string RedirectUri = "https://localhost:44310/Redirect.aspx"; //This is the URL you will redirect to after authentication. It does not need to be publically accessible.
  1. Create a new method InitiateDuo
public async void InitiateDuo(string User)
{
	//Create client using config settings
	Client duoClient = new DuoUniversal.ClientBuilder(ClientId, ClientSecret, ApiHost, RedirectUri).Build();
	Session["client"] = duoClient;


	// Check if Duo seems to be healthy and able to service authentications.
	// If Duo were unhealthy, you could possibly send user to an error page, or implement a fail mode
	var isDuoHealthy = await duoClient.DoHealthCheck();

	// Generate a random state value to tie the authentication steps together
	string state = DuoUniversal.Client.GenerateState();
	// Save the state and username in the session for later
	Session["STATE_SESSION_KEY"] = state;
	Session["USERNAME_SESSION_KEY"] = User;

	// Get the URI of the Duo prompt from the client.  This includes an embedded authentication request.
	string promptUri = duoClient.GenerateAuthUri(User, state);

	// Redirect the user's browser to the Duo prompt.
	// The Duo prompt, after authentication, will redirect back to the configured Redirect URI to complete the authentication flow.
	// In this example, that is /duo_callback, which is implemented in Callback.cshtml.cs.
	Response.Redirect(promptUri,false);

}
  1. Add the following code to the portion where you want to trigger the Duo prompt.
InitiateDuo(User); //User should be the username trying to login.
  1. On your login.aspx page, add Async=”true” to the top
<%@ Page Async="true" Language="C#"......
  1. Create a new Redirect page which Duo will transfer the user to after authenticating with two factor. Add add Using DuoUniversal to this page.
  2. Create a new method DUOResponse
 public async void DUOResponse(string sessionState, string sessionUsername, string receievedstate, string receivedcode)
{
	Client duoClient = (Client)Session["client"];

	//Confirm the original state(from the session) matches the state sent by Duo; this helps p
	if (!sessionState.Equals(receievedstate))
	{
		throw new DuoException("Session state did not match the expected state");
	}
	//Session.Clear();

	IdToken token = await duoClient.ExchangeAuthorizationCodeFor2faResult(receivedcode, sessionUsername);


	if (token.AuthResult.Result == "allow")
	{
		Session["authenticated"] = true;
		Response.Redirect("Home.aspx");

	}
}
  1. In the Page_Load method of the Redirect page add the following code
string sessionState = string.Empty;
if (Session["STATE_SESSION_KEY"] != null)
	sessionState = Session["STATE_SESSION_KEY"].ToString();
else
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Session State Key Missing"));

string sessionUsername = string.Empty;
if (Session["userName"] != null)
	sessionUsername = Session["userName"].ToString();
else
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Session Username Missing"));

string receievedstate = string.Empty;
if (Request.QueryString["state"] != null)
	receievedstate = Request.QueryString["state"];
else
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo State URL Variable Missing"));

string receivedcode = string.Empty;
if (Request.QueryString["code"] != null)
	receivedcode = Request.QueryString["code"];
else
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Code URL Variable Missing"));

if (string.IsNullOrEmpty(sessionState) || string.IsNullOrEmpty(sessionUsername)
	|| string.IsNullOrEmpty(receievedstate) || string.IsNullOrEmpty(receivedcode))
{
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo null items"));
	return;
}

DUOResponse(sessionState, sessionUsername, receievedstate, receivedcode);
  1. On your Redirect.aspx page, add Async=”true” to the top
<%@ Page Async="true" Language="C#"......

Build and test. When you test it the first time you will see the old prompt. If everything works, you can go into Duo Admin console and turn on the new prompt.

Duo Universal C# ASP NET Web Forms Read More »

Directory Services–Cannot Change Password – Constraint Violation nTSecurityDescriptor

 

Recently I ran into an issue where trying to enable or disable the option ‘Cannot Change Password’ in Active Directory in my C# code. Using a Domain Administrator account the code worked perfectly fine, but when it was run under a non-administrator I would get “Constrain Violation Occurred” and the following exception

"0000051B: AtrErr: DSID-030F22B2, #1:\n\t0: 0000051B: DSID-030F22B2, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 20119 (nTSecurityDescriptor)\n"

Now the user in question was delegated full control over the user object which made it more frustrating as it could be done manually within Active Directory Users and Computers. What I did find out is that if the non-administrator user was assigned the owner of the user in the security option the code would work and is what lead me to the answer.

 

You must have the DirectoryEntry.Options.SecurityMasks defined to SecurityMasks.Dacl for it to work for a non-administrator user.

 

Allow Change Password

   1: public static void AllowChangePassword(DirectoryEntry user)

   2:        {

   3:            user.Options.SecurityMasks = SecurityMasks.Dacl;

   4:  

   5:            // Create a Guid that identifies the Change Password right.

   6:            Guid changePasswordGuid =

   7:                new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}");

   8:  

   9:            // Get the ActiveDirectorySecurity for the user.

  10:            ActiveDirectorySecurity userSecurity = user.ObjectSecurity;

  11:  

  12:            // Create a SecurityIdentifier object for "everyone".

  13:            SecurityIdentifier everyoneSid =

  14:                new SecurityIdentifier(WellKnownSidType.WorldSid, null);

  15:  

  16:            // Create a SecurityIdentifier object for "self".

  17:            SecurityIdentifier selfSid =

  18:                new SecurityIdentifier(WellKnownSidType.SelfSid, null);

  19:  

  20:            // Create an access rule to allow everyone the change password 

  21:            // right. 

  22:            // This is used to remove any existing access rules.

  23:            ActiveDirectoryAccessRule allowEveryone =

  24:                new ActiveDirectoryAccessRule(

  25:                    everyoneSid,

  26:                    ActiveDirectoryRights.ExtendedRight,

  27:                    AccessControlType.Allow,

  28:                    changePasswordGuid);

  29:  

  30:            // Create an access rule to deny everyone the change password right.

  31:            ActiveDirectoryAccessRule denyEveryone =

  32:                new ActiveDirectoryAccessRule(

  33:                    everyoneSid,

  34:                    ActiveDirectoryRights.ExtendedRight,

  35:                    AccessControlType.Deny,

  36:                    changePasswordGuid);

  37:  

  38:            // Create an access rule to allow self the change password right.

  39:            // This is used to remove any existing access rules.

  40:            ActiveDirectoryAccessRule allowSelf =

  41:                new ActiveDirectoryAccessRule(

  42:                    selfSid,

  43:                    ActiveDirectoryRights.ExtendedRight,

  44:                    AccessControlType.Allow,

  45:                    changePasswordGuid);

  46:  

  47:            // Create an access rule to deny self the change password right.

  48:            ActiveDirectoryAccessRule denySelf =

  49:                new ActiveDirectoryAccessRule(

  50:                    selfSid,

  51:                    ActiveDirectoryRights.ExtendedRight,

  52:                    AccessControlType.Deny,

  53:                    changePasswordGuid);

  54:  

  55:            // Remove any existing rule that gives "everyone" the change 

  56:            // password right.

  57:            userSecurity.RemoveAccessRuleSpecific(denyEveryone);

  58:  

  59:            // Add a new access rule to deny "everyone" the change password 

  60:            // right.

  61:            userSecurity.AddAccessRule(allowEveryone);

  62:  

  63:            // Remove any existing rule that gives "self" the change password 

  64:            // right.

  65:            userSecurity.RemoveAccessRuleSpecific(denySelf);

  66:  

  67:            // Add a new access rule to deny "self" the change password right.

  68:            userSecurity.AddAccessRule(allowSelf);

  69:  

  70:            // Commit the changes.

  71:            user.CommitChanges();

  72:  

  73:            user.Options.SecurityMasks = SecurityMasks.None;

  74:        }

 

Deny Change Password

   1: public static void DenyChangePassword(DirectoryEntry user)

   2:       {

   3:           user.Options.SecurityMasks = SecurityMasks.Dacl;

   4:           

   5:           // Create a Guid that identifies the Change Password right.

   6:           Guid changePasswordGuid =

   7:               new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}");

   8:  

   9:           // Get the ActiveDirectorySecurity for the user.

  10:           ActiveDirectorySecurity userSecurity = user.ObjectSecurity;

  11:  

  12:           // Create a SecurityIdentifier object for "everyone".

  13:           SecurityIdentifier everyoneSid =

  14:               new SecurityIdentifier(WellKnownSidType.WorldSid, null);

  15:  

  16:           // Create a SecurityIdentifier object for "self".

  17:           SecurityIdentifier selfSid =

  18:               new SecurityIdentifier(WellKnownSidType.SelfSid, null);

  19:  

  20:           // Create an access rule to allow everyone the change password 

  21:           // right. 

  22:           // This is used to remove any existing access rules.

  23:           ActiveDirectoryAccessRule allowEveryone =

  24:               new ActiveDirectoryAccessRule(

  25:                   everyoneSid,

  26:                   ActiveDirectoryRights.ExtendedRight,

  27:                   AccessControlType.Allow,

  28:                   changePasswordGuid);

  29:  

  30:           // Create an access rule to deny everyone the change password right.

  31:           ActiveDirectoryAccessRule denyEveryone =

  32:               new ActiveDirectoryAccessRule(

  33:                   everyoneSid,

  34:                   ActiveDirectoryRights.ExtendedRight,

  35:                   AccessControlType.Deny,

  36:                   changePasswordGuid);

  37:  

  38:           // Create an access rule to allow self the change password right.

  39:           // This is used to remove any existing access rules.

  40:           ActiveDirectoryAccessRule allowSelf =

  41:               new ActiveDirectoryAccessRule(

  42:                   selfSid,

  43:                   ActiveDirectoryRights.ExtendedRight,

  44:                   AccessControlType.Allow,

  45:                   changePasswordGuid);

  46:  

  47:           // Create an access rule to deny self the change password right.

  48:           ActiveDirectoryAccessRule denySelf =

  49:               new ActiveDirectoryAccessRule(

  50:                   selfSid,

  51:                   ActiveDirectoryRights.ExtendedRight,

  52:                   AccessControlType.Deny,

  53:                   changePasswordGuid);

  54:  

  55:           // Remove any existing rule that gives "everyone" the change 

  56:           // password right.

  57:           userSecurity.RemoveAccessRuleSpecific(allowEveryone);

  58:  

  59:           // Add a new access rule to deny "everyone" the change password 

  60:           // right.

  61:           userSecurity.AddAccessRule(denyEveryone);

  62:  

  63:           // Remove any existing rule that gives "self" the change password 

  64:           // right.

  65:           userSecurity.RemoveAccessRuleSpecific(allowSelf);

  66:  

  67:           // Add a new access rule to deny "self" the change password right.

  68:           userSecurity.AddAccessRule(denySelf);

  69:  

  70:           // Commit the changes.

  71:           user.CommitChanges();

  72:  

  73:           user.Options.SecurityMasks = SecurityMasks.None;

  74:       }

Directory Services–Cannot Change Password – Constraint Violation nTSecurityDescriptor Read More »

ASP.NET Web Application Uses Old User Name

Has a user’s name changed recently and they can no longer access a asp.net web application, or the information on the web application still reflects the old user name?

This could be because of the server caching the old username and not looking it up each time. To confirm whether this is what is happening you can disable the caching by doing the following.

 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

Create new DWORD labeled LsaLookupCacheMaxSize and set the value to 0

 

Reference: http://support.microsoft.com/kb/946358

ASP.NET Web Application Uses Old User Name Read More »

WindowsIdentity Impersonation – An attempt was made to reference a token that does not exist

 

Was working on a ASP.NET app to impersonate a domain user to access a network resource and came across the error “An attempt was made to reference a token that does not exist”.

When defining the domain for the following function, make sure it is in the FQDN format. domain.com and not DC=domain,DC=com 

private bool impersonateValidUser(String userName, String domain, String password)

Impersonation Reference: http://support.microsoft.com/kb/306158

WindowsIdentity Impersonation – An attempt was made to reference a token that does not exist Read More »