HELP: Custom sharepoint 2010 login page with FBA and AD

Discussion in 'Programming' started by HumblePie, Dec 13, 2012.

  1. HumblePie

    HumblePie Lifer

    Joined:
    Oct 30, 2000
    Messages:
    12,880
    Likes Received:
    11
    Okay, I am trying to create a custom login page for a 2010 site. First off, I'm practically doing every thing in regards to this site but still have to work with certain configurations.

    Let me step through what I have done thus far and what we have setup. The initial server and settings I did not do. So I've had to go back in and reconfigure quite a bit to get Forms Based Authentication with Active Directory working.

    As far as the basic configuration goes I have a Sharepoint 2010 server and a separate Active Directory server. I configured my Sharepoint server to use FBA with active directory following this page.

    http://blogs.msdn.com/b/sridhara/ar...ith-active-directory-membership-provider.aspx

    Basically I removed all the other member providers that someone else had screwed around with in the various web configs. Found out some of those weren't setup right and causing problems. So I modified the central admin web config as well as the security token web config and the site I am going to put the custom login in page web config. All have the proper active directory server connection string and membership provider creation using that connection string.

    I configured the site off the port I want to use to use FBA and call upon the active direct member provider I setup in the web configs. I also set up the people picker wild card for the membership as well.

    All that is now hunky dory now, but it took several days to get everything working for just that. Mainly because I had zero idea initially what I was doing and how to do all that. So great learning experience.

    I came across two different ways through the magic of the internet to implement a basic forms custom login page. I created Visual Studio projects for both and tried them both. Both work, but are very basic.

    Now there is where I'm stuck. My requirement is to use this custom login page to deal with password expiration of accounts from active directory. I can't find jack for info on how to do this or even where to start.

    Here are the two ways I saw to create a custom login page.

    1) http://www.mssharepointtips.com/tip.asp?id=1093
    This site shows how to create a basic forms page that extends out System.Web.UI.Page which is the layout base page. With this version, no sharepoint master page is used. This is handy for debugging on my site as I don't have to do the normal login first then direct myself to the page Visual Studio deploys to test my login page.

    This page reacts of the Login_Click button and uses the SPClaimsUtility to authenticate through FBA and the AD linked to it the user name and password typed in. For basic authentication and having a security cookie generated to log into the site this works.

    AND this is the second way I tried

    2) http://davidlozzi.com/2011/07/15/sh...e-login-page-with-forms-based-authentication/
    This site shows how to create a custom login page by using the basic simple master page and extending out the FormsSignInPage class. This page uses default functionality built into the wss resources for authenticating the user. Truth be told I am not exactly sure how this page works but it does. The problem is that since this page uses the master page it makes debugging a bit more difficult. If I build in debug mode and use Visual Studio to deploy to run in debug mode, I have to log in normally first, then navigate to the /_layouts/MyProjetFolder/Mypage.aspx (or whatever I have named things) to view the login page I created. This does work, just more of an annoyance compared with the first way.


    However, I am not sure where to proceed from here. What I want to do is not redirect the user after authenticating them. First, I want to see how long before their password expires. Or if authentication failed, check to see if the failure was from an expired password. Then if need be redirect the user to another page to change/update their password. I also want to grab some additional meta data stored in Active Directory about the user trying to login such as their email address and group so I can redirect them to certain default login pages for that user.


    Anyone know what I should do to proceed from here?
     
  2. HumblePie

    HumblePie Lifer

    Joined:
    Oct 30, 2000
    Messages:
    12,880
    Likes Received:
    11
    Here is an update after much research on my part to solve this problem of creating a nice sharepoint custom log in page for multiple potential customers using an active directory as a single sign on.

    The 1st way is what I needed to go with in order to get the properties correctly from the active directory server.

    Since active directory is not running as a role on the same server that sharepoint is, finding the RIGHT code to at least get at the active directory object model was a PITA.

    So with my log in page, I have to make sure I am referencing and using System.Data as well as the System.DirectoryServices and System.DirectoryServices.ActiveDirectory for my work.

    Here is the code I am using to get the Domain level max password age.

    Code:
    // I editted out here my actual LDAP server connection string and the admin username
    // and password to connect to the ldap server. If you have a setup of a separate ldap 
    // server without a role and require admin privs to get info then substitute the correct info here
    
    DirectoryEntry ldapDomain = new DirectoryEntry("LDAP://myldapserver.com", "adminusername", "adminpassword"); 
    DirectorySearcher ds = new DirectorySearcher(ldapDomain,
                                                      "(maxPwdAge=*)",
                                                      new string[] {"maxPwdAge"},
                                                      SearchScope.Base);
    
    SearchResult ldapDirSearcher = ds.FindOne();
    
    TimeSpan maxPwdAge = TimeSpan.MinValue;
    
    long maxDays = 0;
    
    if (ldapDirSearcher.Properties.Contains("maxPwdAge"))
    {
         maxPwdAge = TimeSpan.FromTicks(Math.Abs((long)ldapDirSearcher.Properties["maxPwdAge"][0]));
         maxDays = ((long)ldapDirSearcher.Properties["maxPwdAge"][0]) / -864000000000;
    }
    

    So this at least gets my the max password age of the domain server. I can compare that to the user LastPasswordChangedDate property to determine when the user's password will expire.

    But there is a kink. Our setup has various customers in their own OU (organization unit) folder within the active directory. Each OU has it's own group they are assigned to. This is so people belonging to one to one group can't get where they are suppose to. good security. With windows 2008 password policies can be applied at the group level that will override the domain's global password policy. This is called FGPP (Fine grained password policy), and I'm stuck trying to figure out how to get this information for a particular user programically in c#. I know that using FGPP one creates as PSO (password settings object) using the active directory server tools such as ADIS (active directory information script) tool or ADUC (active directory users and computers) tool.

    One would think something like has more information out there on how to do than it currently is. Right now this whole exercise in finding out how to build a custom sharepoint login page has been quite the endeavor.

    I know that I need to get at the msDS-ResultantPSO attribute of the group or user in question. How to get that, or what I need to do with that object to obtain the the info I seek is another matter.

    So for example the
     
    #2 HumblePie, Dec 18, 2012
    Last edited: Jan 3, 2013
  3. HumblePie

    HumblePie Lifer

    Joined:
    Oct 30, 2000
    Messages:
    12,880
    Likes Received:
    11
    Well to keep things updated, stuff is coming along. Still not completely done yet, mostly because of the holidays, but it's all being worked on.

    Part of what has helped me is that my company has not decided on using a FGPP setup for individual or OU level max password ages. So it's going to be the global settings.

    I was a bit stuck on trying to figure out if an account that was unable to log in and the reason for such. This was a bit more of a headache than I anticipated. I am not sure I have it correct, but this is what I do have for the code.

    Code:
    bool status = SPClaimsUtility.AuthenticateFormsUser(Page.Request.Url, UserName.Text, Password.Text);
    
                    if (!status)// if auth failed
                    {
                        // check for why the authentication failed. If for password expiration, redirect to the password
                        // change options
    
                         DirectoryEntry ldapDomain = new DirectoryEntry("LDAP://MYDOMAIN.COM", "adminaccount", "adminpassword"); // note use the real LDAP connection string for your stuff
    
                        // If an active directory server object is found, then check for the max password age
                        // of the username provided. First check if there is a FGPP (fine grain password policy) is
                        // in place for the user. Do this by creating for and searching for the sAMAccountName based
                        // of the username and looking for any maxpwdage properties assigned.
                         if (ldapDomain != null)
                         {
                             DirectorySearcher dirSearcherForUser = new DirectorySearcher(ldapDomain);
    
                             dirSearcherForUser.Filter = "(sAMAccountName=" + UserName.Text + ")";
    
                             dirSearcherForUser.PropertiesToLoad.Add("cn");
    
                             SearchResult findResult = dirSearcherForUser.FindOne();
                             if (findResult == null)
                             {
                                 lblError.Text = "User account not found.";
                                 return;
                             }
    
                             DirectoryEntry entry = findResult.GetDirectoryEntry();
    
                             //long pwdLastSetValue = Math.Abs((long)entry.Properties["pwdLastSet"].Value);
                             string attribName = "msDS-User-Account-Control-Computed";
                             entry.RefreshCache(new string[] { attribName });
                             AdsUserFlags userFlags = (AdsUserFlags)entry.Properties["msDS-User-Account-Control-Computed"].Value;
    
                             if (userFlags.ToString().Contains(AdsUserFlags.PasswordExpired.ToString()))
                             {
                                 lblError.Text = "<img alt='' src='error-icon.png' style='width:24px; height:24px; margin-right:5px; vertical-align:bottom'/>Password expired.";
                             }
                         }
    
    also the flags I'm checking against are these.

    Code:
    [Flags]
            internal enum AdsUserFlags
            {
                Script = 1,                          // 0x1
                AccountDisabled = 2,                 // 0x2
                HomeDirectoryRequired = 8,           // 0x8
                AccountLockedOut = 16,               // 0x10
                PasswordNotRequired = 32,            // 0x20
                PasswordCannotChange = 64,           // 0x40
                EncryptedTextPasswordAllowed = 128, // 0x80
                TempDuplicateAccount = 256,          // 0x100
                NormalAccount = 512,                 // 0x200
                InterDomainTrustAccount = 2048,      // 0x800
                WorkstationTrustAccount = 4096,      // 0x1000
                ServerTrustAccount = 8192,           // 0x2000
                PasswordDoesNotExpire = 65536,       // 0x10000
                MnsLogonAccount = 131072,            // 0x20000
                SmartCardRequired = 262144,          // 0x40000
                TrustedForDelegation = 524288,       // 0x80000
                AccountNotDelegated = 1048576,       // 0x100000
                UseDesKeyOnly = 2097152,              // 0x200000
                DontRequirePreauth = 4194304,         // 0x400000
                PasswordExpired = 8388608,           // 0x800000
                TrustedToAuthenticateForDelegation = 16777216, // 0x1000000
                NoAuthDataRequired = 33554432        // 0x2000000       
            }
    
    here is a screen shot of the page I got going so far.

    [​IMG]

    Still a work in progress though. I'm working on the password change and unlocking of accounts if the password was expired and needs a reset. All the design work I did myself for now.
     
    #3 HumblePie, Jan 2, 2013
    Last edited: Jan 3, 2013
Loading...