Friday, February 8, 2013

Using Active Directory authentication in MVC4

We bought the new book Pro ASP.NET MVC4 by Adam Freeman, which is a great book and very well written.  However, in the authentication section, he assumes (probably rightly) most folks will be writing applications for the broader internet and want to implement a stand-alone means of authentication (probably wrongly).  It seems to me that these days you deal your site out unless people can single sign-on in some form or fashion using an existing google or facebook account or other account.  Anyhow, for our Project Tracker application, we're all in-house and Adam didn't have any explanation for setting up AD Authentication, so this is how it is done.

First, you'll need to find the LDAP url you'll use to communicate with the windows domain server that hosts your Active Directory accounts.  If you don't know it, open a command prompt and type:

gpresult /r

That'll give you a bunch of information related to your account and will include a line like:

USER SETTINGS
--------------
    CN=Ken Burcham,OU=Users,OU=Geographic Information Systems,OU=Office of Infor
mation Technology,OU=Administration,OU=CTUIR,DC=mailcomm,DC=ctuir,DC=com
    Last time Group Policy was applied: 2/7/2013 at 1:19:28 PM
    Group Policy was applied from:      CTUIR-DCX.mailcomm.ctuir.com
    Domain Name:                        MAILCOMM


From this, you'll need to construct an LDAP connection string and add it to the connectionStrings section of  your web.config.  For me, was something like this:



<add name="ADConnectionString" connectionString ="LDAP://CTUIR-DCX.mailcomm.ctuir.com:389/DC=mailcomm,DC=ctuir,DC=com" />


Note that the default port is 389 but if you are connecting from external to your network you'll certainly want to connect using SSL which for LDAP is port 636. Also note that the order of the elements is important. :) You'll also need to add the following section to your web.config to enable the ADMembershipProvider:

<membership defaultProvider="ADMembershipProvider">
      <providers>
        <clear/>
        <add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName"/>
      </providers>
    </membership>

Next, configure your application to handle authentication by adding this to your Global.asax.cs file:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new System.Web.Mvc.AuthorizeAttribute());
        }

Now you need to connect the hoses in your AccountController. This section is mostly following the instructions posted by Ricardo Sanchez here: How to secure your asp net mvc application

[Authorize]
    public class AccountController : Controller
    {
        //
        // GET: /Account/Login

        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/Login

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if(Membership.ValidateUser(model.UserName, model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    if(Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
                        && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index","Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("","The user name or password provided is incorrect.");
                }
            }
            
            return View(model);
        }

        //
        // POST: /Account/LogOff

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

That's about it. One last thing, I used this at the top of my _loginPartial.cshtml which worked nicely to display the currently logged in user:

@(User.Identity.IsAuthenticated ? "Hello, " + User.Identity.Name : "Not logged in.")

Well, I didn't find all of this in one place, so hopefully it'll help someone else! :)

10 comments:

Unknown said...

I'm trying to combine Ricardo's example with the code you have provided. However, there are some naming inconsistencies. For instance, Ricardo Sanchez's example uses LogOnModel model while your example uses a LoginModel model. Did MVC change the models between versions?

Unknown said...
This comment has been removed by the author.
Charles said...
This comment has been removed by the author.
Charles said...
This comment has been removed by the author.
Charles said...

Thank you for your post, Ken!
Note that if you enter or edit your connection string in the GUI editor (Properties --> Settings) that your key will be silently renamed to something like [PROJECTNAME].Properties.Settings.ADConnectionString
rather than just ADConnectionString.

I found it easiest to let it do this and change the name accordingly in the <membership> section added under <system.web> like so:

<add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="[PROJECTNAME].Properties.Settings.ADConnectionString" attributeMapUsername="sAMAccountName"/>

Unknown said...

Thank you very much for your post !
we followed your instructions and it worked properly

Ken Burcham said...

@Troy -- it is whatever you name it yourself.

@Charles -- thanks for pointing that out. I handcode whenever possible, but that woulda been a big gotcha!

@Itzik -- Great! So glad it was helpful post!

Cesar Vegax said...
This comment has been removed by the author.
Cesar Vegax said...

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 53: providers>
Line 54: clear/>
Line 55: add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName"/>
Line 56: /providers>
Line 57: /membership>


Source File: C:\inetpub\wwwroot\PPRWebsite\web.config Line: 55

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.1022

Cesar Vegax said...

SOlution:
I changed the Identity of the Application Pool from "ApplicationPoolIdentity" to "NetworkService" and everything works great now.