MSDN Deep Dive: Building Killer ASP.NET Applications…


Lot’s of requests for the code from the Forms Login demo… here it is…


LDAPAuth.cs:



using System;


using System.Text;


using System.Collections;


using System.DirectoryServices;


using System.Web;


namespace FormsAuthAD


{



public class LDAPAuthentication


{


private string _path;


private string _filterAttribute;


private string domainusername, password, rootdse, uname;


public LDAPAuthentication()


{


}


public LDAPAuthentication(string path)


{



_path=path;


}


public bool IsAuthenticated(string domain, string username, string pwd)


{



string domainAndUsername = domain + @”\” + username;


uname=username;


domainusername=domainAndUsername;


password = pwd;


rootdse=_path;


DirectoryEntry entry = new DirectoryEntry( _path, domainAndUsername, pwd);


try


{



// Bind to the native AdsObject to force authentication.


Object obj = entry.NativeObject;


DirectorySearcher search = new DirectorySearcher(entry);


search.Filter = “(SAMAccountName=” + username + “)”;


search.PropertiesToLoad.Add(“CN”);


SearchResult result = search.FindOne();


if(null == result)


{



return false;


}


// Update the new path to the user in the directory


_path = result.Path;


_filterAttribute = (String)result.Properties[“cn”][0];


}


catch (Exception ex)


{



throw new Exception(“Error authenticating user. ” + ex.Message);


}


return true;


}


public string GetGroups(LDAPAuthentication adAuth)


{



//does not work for users not part of at least 2 groups


//for example, will not work for users that are only part of the


//domain users group


string resultset=””;


StringBuilder groupNames = new StringBuilder();


try


{



//bind to AD on the new path


DirectoryEntry entry2 = new DirectoryEntry( rootdse, domainusername, password);


//create a directorysearcher object off the directoryentry object


DirectorySearcher mysearch = new DirectorySearcher(entry2);


//find the username under the person category


mysearch.Filter= “(&(anr=”+ uname + “)(objectCategory=person))”;


//we want to load the memberOf property


mysearch.PropertiesToLoad.Add(“memberOf”);


//find an instance


SearchResult result = mysearch.FindOne();


String dn;


int equalsIndex, commaIndex;


int propertyCount = result.Properties[“memberOf”].Count;


//enumerate through the properties loaded


for(int propertyCounter = 0; propertyCounter < propertyCount; propertyCounter++)


{



dn = (String)result.Properties[“memberOf”][propertyCounter];


equalsIndex = dn.IndexOf(“=”, 1);


commaIndex = dn.IndexOf(“,”, 1);


if(-1 == equalsIndex)


{


return null;


}


//create a list seperated by | character


groupNames.Append(dn.Substring((equalsIndex + 1), (commaIndex – equalsIndex) – 1));


groupNames.Append(“|”);


}


}


catch(Exception ex)


{



throw new Exception(“Error obtaining group names. ” + ex.Message);


}


return groupNames.ToString();


}


}


}


 


web.config:


<?xml version=”1.0″ encoding=”utf-8″ ?>


<configuration>



<system.web>


 


<compilation defaultLanguage=”c#” debug=”true” />


<customErrors mode=”RemoteOnly” />


<authentication mode=”Forms”>



<forms loginUrl=”logon.aspx” name=”adAuthCookie” timeout=”60″ path=”/”>


</forms>


</authentication>



<authorization>



<deny users=”?” />


<allow users=”*” />


</authorization>


<identity impersonate=”true” />


<trace enabled=”false” requestLimit=”10″ pageOutput=”false” traceMode=”SortByTime” localOnly=”true” />


<sessionState mode=”InProc” stateConnectionString=”tcpip=127.0.0.1:42424″ sqlConnectionString=”data source=127.0.0.1;Trusted_Connection=yes”  cookieless=”false” timeout=”20″ />


<globalization requestEncoding=”utf-8″  responseEncoding=”utf-8″ />


<httpHandlers>



<add verb=”*” path=”*.sqlx” type=”ProAspNet.CS.Ch23.QueryHandler,QueryHandler”/>



</httpHandlers>



</system.web>


</configuration>


 


Logon.aspx.cs:


private void btnLogon_Click(object sender, System.EventArgs e)


{


// Path to you LDAP directory server.


// Contact your network administrator to obtain a valid path.


string adPath = “LDAP://mycompany.com/DC=mycompany,DC=com”;


LDAPAuthentication adAuth = new LDAPAuthentication(adPath);


try


{


if(true == adAuth.IsAuthenticated(txtDomainName.Text,


txtUserName.Text,


txtPassword.Text))


{


string groups = adAuth.GetGroups(adAuth);


// Create the authetication ticket


FormsAuthenticationTicket authTicket =


new FormsAuthenticationTicket(1, // version


txtUserName.Text,


DateTime.Now,


DateTime.Now.AddMinutes(60),


false, groups);


// Now encrypt the ticket.


string encryptedTicket = FormsAuthentication.Encrypt(authTicket);


// Create a cookie and add the encrypted ticket to the


// cookie as data.


HttpCookie authCookie =


new HttpCookie(FormsAuthentication.FormsCookieName,


encryptedTicket);


// Add the cookie to the outgoing cookies collection.


Response.Cookies.Add(authCookie);


// Redirect the user to the originally requested page


Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,false));


}


else


{


lblError.Text = “Authentication failed, check username and password. Path is : ” + adPath + ” “;


}


}


catch(Exception ex)


{


lblError.Text = “Error authenticating. ” + ex.Message;


}


}


 


global.asax.cs:


// Extract the forms authentication cookie


string cookieName = FormsAuthentication.FormsCookieName;


HttpCookie authCookie = Context.Request.Cookies[cookieName];


if(null == authCookie)


{


// There is no authentication cookie.


return;


}


FormsAuthenticationTicket authTicket = null;


try


{


authTicket = FormsAuthentication.Decrypt(authCookie.Value);


}


catch(Exception ex)


{


// Log exception details (omitted for simplicity)


return;


}


if (null == authTicket)


{


// Cookie failed to decrypt.


return;


}


// When the ticket was created, the UserData property was assigned a


// pipe delimited string of group names.


String[] groups = authTicket.UserData.Split(new char[]{‘|’});


// Create an Identity object


GenericIdentity id = new GenericIdentity(authTicket.Name,


“LdapAuthentication”);


// This principal will flow throughout the request.


for (int i=0;i<groups.Length;i++)


{


string cn = “CN=”;


groups[i]=groups[i].Substring(3);


groups[i]=groups[i].Substring(0, groups[i].IndexOf(“,”));


}


GenericPrincipal principal = new GenericPrincipal(id, groups);


// Attach the new principal object to the current HttpContext object


Context.User = principal;


 


Comments (5)

  1. rumen stankov (a) telerik com says:

    Thanks a lot for the code! I’ve spent a few minutes trying to crack this exact problem yesterday but had hard times even to get started.

    Is this the only way to proceed:

    mysearch.Filter= "(&(anr="+ uname + ")(objectCategory=person))";

    .NET style, I would have expected strongly typed properties for filter, i.e. mysearch.Fiter.Category = Category.Person, so I can get my job done without learning ActiveDirectory format strings.

    Thanks for the info once again.

  2. Can a similar technique to authenticate users be done (and/or read/write their user properties) without AD or AD/AM on a Windows 2000 Server?

    The following code will fail when the properties are enumerated even if the username and password given are those of an administrator:

    string path = @"WinNT://" + SystemInformation.ComputerName;

    string su = "admin";

    string pass = "adminpass";

    DirectoryEntry objDirEnt = new DirectoryEntry(path,su,pass);

    this.lblMessage2.Text = objDirEnt.Name + " = " + objDirEnt.Path + " = " + objDirEnt.SchemaClassName;

    foreach(String Key in objDirEnt.Properties.PropertyNames) {

    this.lblMessage2.Text += Key+"<br />";

    foreach(Object objValue in objDirEnt.Properties[Key]) {

    this.lblMessage2.Text += objValue+"<br />";

    }

    }

  3. Russel Krause says:

    I found a solution to my previous posting.

    We are building a ASP.NET website to manage adding/updating/deleting Windows/SQL/IIs users on-line. I needed a way to have the ASP.NET process "impersonate" an administrator to perform many of the the Windows operations. I found a solution provided by Robert Chartier of Santra Technology (http://www.santra.com) as described in his paper at http://www.15seconds.com/issue/020730.htm which I have repeated below (with full credit to Robert Chartier). Email me mailto:krauser@macewan.ca:

    Rob uses two methods from InteropServices advapi32.dll two define two of his own working methods impersonateValidUser() and undoImpersonation() which you can find in his referenced article:

    #region setup impersonation via interop

    //need to import from COM via InteropServices to do the impersonation when saving the details

    public const int LOGON32_LOGON_INTERACTIVE = 2;

    public const int LOGON32_PROVIDER_DEFAULT = 0;

    System.Security.Principal.WindowsImpersonationContext impersonationContext;

    [DllImport("advapi32.dll", CharSet=CharSet.Auto)]public static extern int LogonUser(String lpszUserName,

    String lpszDomain,String lpszPassword,int dwLogonType,int dwLogonProvider,ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto, SetLastError=true)]public

    extern static int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);

    #endregion

    And impersonates the administrator temporarily while making the prtected AD calls:

    if(impersonateValidUser(this.LoginUsername, this.DomainName, this.loginPassword)) {

    //Insert your code that runs under the security context of a specific user here.

    //don’t forget to undo the impersonation

    undoImpersonation();

    } else {

    //Your impersonation failed. Therefore, include a fail-safe mechanism here.

    }

    Happy trails!