Pages

Monday, April 25, 2011

ASP.NET Forms Based Authentication with Active Directory

If you've ever used Visual Studio's 2010 the default template for an ASP.NET Web Site is a really nice. It allows you to give users accounts based on SQL accounts it creates. I however wanted to use Active  Directory instead of SQL and do the user authentication there instead of setting it on the entire IIS site. This also allows you to have a much prettier login page rather than the normal web browser's "basic access authentication" prompt.

I figured that changing the ASP 4.0 WebSite to use Active Directory would be be a GUI wizard to do it but found a large amount of confusion on the subject when I was researching how. After Figuring it out for myself I figured I'd write a guide on how.

Setup your machine
Serveral of the problems I observed were people had problems with their website and Visual Studio install before they even started with change the website to use AD. Start by creating a WebSite from the "ASP.NET Web Site" using .Net Framework 4. Don't make a single change and build and run the project. Does the site come up. Can you register a user account and then login to that user account. If not don't proceed any further till you can.

The cause for this for many people was that the website went to use the SQLEXPRESS instance only to find that it wasn't installed or running. As a result you'll get the following error from IIS.

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Changing the Template to use Active Directory
Nearly everything nessory is done in the web sites root web.config file. Below is a copy of the complete file.

  • Add connectionStrings "ADService" to domain your going to use. example "LDAP://nku.edu" (note i tried to alter this to use )
  • Notice that we remove the AspNetSqlMembershipProvider and added AspNetActiveDirectoryMembershipProvider. 
  • Making sure to set AspNetActiveDirectoryMembershipProvider as the default membership provider.
  • Setting attributeMapUsername to sAMAccountName 


<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
 <connectionStrings>
  <add name="ADService" connectionString="LDAP://Domainname.com"/> 
  <add name="ApplicationServices" 
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true" 
         providerName="System.Data.SqlClient"/>
 </connectionStrings>
 <system.web>
  <compilation debug="true" targetFramework="4.0">
   <assemblies>
    <add assembly="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
      </assemblies>
    </compilation>
  <authentication mode="Forms">
   <forms loginUrl="~/Account/Login.aspx" timeout="2880"/>
  </authentication>
  <membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
   <providers>
    <clear/>
    <!-- <add name="AspNetSqlMembershipProvider" 
             type="System.Web.Security.SqlMembershipProvider" 
             connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/"/> -->
    <add name="AspNetActiveDirectoryMembershipProvider"
             type="System.Web.Security.ActiveDirectoryMembershipProvider,  System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
             connectionStringName="ADService" 
             attributeMapUsername="sAMAccountName"/>
   </providers>
  </membership>
  <profile>
   <providers>
    <clear/>
    <add name="AspNetSqlProfileProvider" 
             type="System.Web.Profile.SqlProfileProvider"
             connectionStringName="ApplicationServices"
             applicationName="/"/>
   </providers>
  </profile>
  <roleManager enabled="false">
   <providers>
    <clear/>
    <add name="AspNetSqlRoleProvider" 
             type="System.Web.Security.SqlRoleProvider" 
             connectionStringName="ApplicationServices"
             applicationName="/"/>
    <add name="AspNetWindowsTokenRoleProvider"
             type="System.Web.Security.WindowsTokenRoleProvider"
             applicationName="/"/>
   </providers>
  </roleManager>
 </system.web>
 <system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
 </system.webServer>
</configuration>



We also want to create content where the user has to be authenticated to view. Create a new folder to the project. , i called mine "Content". Then add a new item to the folder, selecting "web config". Alter the new web.config to match below.

<configuration>
    <system.web>
      <authorization>
        <!-- deny = ? means deny unauthenticated users -->
        <deny users="?"/>
      </authorization>
    </system.web>
</configuration>

This means that any content inside this folder requires the users first be authenticated before they can access. Any link to this content will first prompt an unauthenticated user to login and then be redirected to the linked content.


Cleanup
There were a several pages and links we can remove as the aren't used since we are using AD to authenticate via forms based authentication. You could comment them out if you ever want to switch back to SQL based users.
  • Remove the "Register User" hyperLink from Login.aspx and delete the Register.aspx file.
  • Change the Site.Master using   we change the tabs users can see depending on if they are logged in or not.
Source
I've included a Visual Studio 2010 Project using Framework 4 that may be useful to view how it works and the changes I made.

Download  - Web Site Using AD Forms Based Authentication

Pictures showing the user Login


Anonymous User Viewing the Site

User accessing Login Page or Content that requires Login
Authenticated User Viewing Site

Links
http://stackoverflow.com/questions/895002/asp-net-active-directory-membership-provider-and-sql-profile-provider/5779884#5779884
http://www.howtodothings.com/computers/a792-aspnet-forms-authentication-with-roles.html
http://msdn.microsoft.com/en-us/library/ff648341.aspx#paght000012_step1


43 comments:

  1. brilliant just what ive been looking for. thank you

    ReplyDelete
  2. Thanks, but one question.
    When the user log in, do they use there AD id, and there AD password then? E.g.
    UserId: group\willpe
    Password: abc123

    ReplyDelete
  3. Nice post. However I got issues trying to use LDAP://localhost, I keep getting an error: Unable to establish secure connection with the server. I even setup the web applicacion with SSL security.

    Any advices are welcome!

    Thanks!

    ReplyDelete
  4. Thank you, very useful post.
    But i did tried as said above, it worked well in VS 2010 . But when i hosted the application on IIS 5.0 (Windows XP), it is throwing error.

    Any suggestions please...?

    Thanks
    Phani Krishna

    ReplyDelete
  5. Thank you for the simple summary. There is tons of information on the internet about this subject, but I do not understand why everyone else makes it so complicated. Your directions worked perfectly.

    ReplyDelete
  6. To Krishna, Did you create and change the IIS app pool to use ASP.NET 4.0?

    ReplyDelete
  7. Thanks JM, I had the same problem my self. I found pieces of the solution all over but no where did i find a complete guide or example with all the pieces.

    ReplyDelete
  8. @SmokedIronMadeOct : I was having the same issue and I managed to solve it by changing the context in which my site ran. Since I am running my site on a stand-alone server (not part of a domain) I now rely on pass-through authentication. I setup a local account on the web server with the same username and password as an existing Active Directory account in the Domain Users group. I think this creates an artificial trust between the domain and the web server's workgroup. I am not sure if this is the most secure or elegant way to go about this but it is working for me. I will continue to research as time allows to ensure this is a secure way to proceed. If you have any notes please post.

    ReplyDelete
  9. Thanks for posting your experience Chris, it helped a ton!

    ReplyDelete
  10. Hi SmokedIronMade, I had the same problem "Unable to establish secure connection with the server" just add this 2 line under attributeMapUsername, connectionUsername="domain\username", connectionPassword="password". This credential are then used for the connection.

    ReplyDelete
  11. @findout, I later found the same alternative when researching. The method works however because a domain account password is stored in plain text with this method the recommendation is to encrypt the relevant section of the web.config to protect unauthorized access to Active Directory.

    ReplyDelete
  12. Thanks for the Post! It helps me a lot!

    ReplyDelete
  13. It was really helpful, have been on this for 3 weeks but now have a clear understanding on it now.

    ReplyDelete
  14. I'm an idiot! I followed your (and several other) guideline exactly but did not get it to work. After 2 hours I figured out: I always tried to login as 'Domain\Username' but it should be only 'Username' .... Damn me :-( So thanks a lot for your guidelines!

    ReplyDelete
  15. Thanks so much! This was SO helpful.

    ReplyDelete
  16. It works but I have a really dumb question. I want to save the UserName in a session variable. So on a postback in the page_load event I simply put this:

    Session["UserName"] = LoginUser.UserName;

    I put a breakpoint on this line but the value of LoginUser.UserName is "". What an I doing wrong?

    ReplyDelete
    Replies
    1. Likely you are setting the Session["UserName"] before the user is logged in. Add the following and try setting the session info after the user is logged in.

      Change the Asp:Login and add the following.
      asp:Login ID="LoginUser" runat="server"
      EnableViewState="false" RenderOuterTable="false"
      OnLoginError=OnLoginError OnLoggedIn=OnLoggedIn


      Now in the code behind create this functions.
      protected void OnLoginError(object sender, EventArgs e) {}
      protected void OnLoggedIn(object sender, EventArgs e) {}


      With these you should have two functions that you can use to do stuff when a login fails or a sucessfuly login.

      Delete
  17. Hey, mahalo plenty for posting this article! I was tasked with creating a .NET form authorization using AD and was banging my head against my cube wall until I came upon this. It's also my very first .NET program...! Question: To show their displayname from AD instead of the login, what do I need to do?

    ReplyDelete
  18. thanks, very helpful

    ReplyDelete
  19. In my environment I had to supply the port (:389).

    But now that I'm authenticated, the second web.config is not allowed unless I make the Content directory an application. Is that how your prescribed solution is supposed to work? If so, fine, but if not, what did I do wrong? Thanks.

    ReplyDelete
  20. Thank you very much. That did it for me.

    ReplyDelete
  21. Thank you so much. This code was really help me alot...

    ReplyDelete
  22. You are the bomb!!! :-). Thanks ALOT! Just what i needed...

    ReplyDelete
  23. This comment has been removed by the author.

    ReplyDelete
  24. Hi Chris, I have this working. My only problem now is figuring out how to restrict access to pages and directories based off of an active directory security group. For example, I only want the people in the management security group to access the management pages and directory of the site. Can you assist?

    ReplyDelete
  25. Thank you! Thank you!!!

    ReplyDelete
  26. Can we work on extending this post? There is a great deal of confusion with using AD Groups and a Role provider. I have been doing a lot of reading in the last couple of days and everything I read says you have to ditch forms Authentication for Windows based authentication if you want to use Groups or Roles.
    I would like to use forms based authentication and provide tabs or menu items to users who belong to an AD Group. Can you explain why I have to move away from Forms authentication to make this work? Just the same I also notice it seems the web server must also belong to or be on the same domain for windows authentication to work properly. Can you also confirm this?

    ReplyDelete
  27. Hey Mike,

    Thanks for your comment but honestly I don't know how to do it any better and not currently working it. I created that template for 2 sites i needed and then inside the login page i wrote AD code to check if the user was a member of the groups i was worried about and stored the result in session information. Not an elegant solution and i'm sure there's a better way just don't know what it is. I am working on something else i may replace it with, using a WIF/ADFS claims based login for ASP.NET MVC 4 that i might replace the sites based on that model.

    -Chris

    ReplyDelete
    Replies
    1. Hi Chris,

      I am working on the exactly similar requirement fro my project so can you share your code so that we can collaborate.

      Soumen

      Delete
  28. hello chris, i need your generous advice on my situation. i followed your instructions and it works perfectly on my pc. When I copy all of my files under wwwroot/comms to the server (wwwroot/comms, it gives me this error. Please check it below. I don't know how to resolve this. I hope you could shed your brilliant solution in my situation.

    PC Specs: Windows 7, MS VS 2010 and SQL Server 2008.

    Server Specs: WinSvr2008 R2, SQL Server 2012.

    Thanks a lot in advance. Jim.

    Server Error in '/comms' Application.

    --------------------------------------------------------------------------------

    Configuration Error
    Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

    Parser Error Message: An operations error occurred.


    Source Error:



    Line 35: maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
    Line 36: applicationName="/"/>-->
    Line 37: Line 38:
    Line 39:
    .

    ReplyDelete
  29. chris, i am reposting the error cause it's not showing the right error as shown in my browser:

    Line 31:
    Line 32:
    Line 33:
    Line 34:
    Line 35: </membership

    ReplyDelete
  30. sir, i don't understand but it's not posting the line 33: which is:

    (add name="AspNetActiveDirectoryMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, .... etc)


    it is under
    (rolemanager enabled="false")
    (providers)
    (clear/)
    then the long line... that is the red line in my browser.
    (/providers)
    (/rolemanager)

    - i put parenthesis () because <> will delete the whole line.

    in my pc it is working fine. but in the server, that long line gave error to me. is there any missing or compatibility related?

    thanks. Jim.

    ReplyDelete
  31. Dear Friend,

    Do you have any updated code using AD groups? I can´t find anywhere.

    Regards,
    Marcos

    ReplyDelete
  32. I just tried that example. I did a PUBLISH into a directory.
    I had some other sub directories below the main. the PUBLISH
    DELETED ALL of my SUBDIRECTORIES!!!!!

    It does have a message "Do you want to delete existing files..."
    Since I did it for the 2nd time, I thought it was just the previous files.

    ReplyDelete
  33. Is there a need to sanitize the user input against injection attack using the suggested ADProvider? I'm kind of worried about it.

    ReplyDelete
  34. Very helpful!!!
    Spent the whole day trying to make this work by following other how-to pages but didn't succeed. Upon reading your guide it took me only minutes to correct what I was doing wrong.

    Thank you very much!!!

    ReplyDelete
  35. I am so happy to have found this post. You have saved my bacon as I just was told 'Let's change our whole authentication scheme', mere days from deployment! You've already mapped out 3/4 of what I need to do. Thank you so much for sharing!!!

    ReplyDelete
  36. Hi Chris,
    Sorry for my previous post it was a type in my code, the authentication is working fine
    But when I tried to change my password through your code ( changepassword.aspx) I am getting this error "Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied. (Exception from HRESULT: 0x80070547) " Can I solve this issue?

    ReplyDelete

Please leave a comment; someone, anyone!