Tag: Authentication

Password Policy Check in ASP.NET Membership

There are 3 attributes in ASP.NET Membership Providers for configuring the password policy check.

1. minRequiredPasswordLength
Specifies the minimum number of characters that are required in a password. The default is 7.

2. minRequiredNonalphanumericCharacters
Specifies the minimum number of special characters that must be present in a valid password. The default is 1.

3. passwordStrengthRegularExpression
Specifies the regular expression that is used to evaluate a password. The default is an empty string (“”).

The following is a sample of ASP.NET Membership Provider Configuration.

<membership defaultProvider="SqlMembershipProvider" userIsOnlineTimeWindow="30">
  <providers>
    <clear />
    <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" applicationName="MyApplication" 
      minRequiredPasswordLength="8" 
      minRequiredNonalphanumericCharacters="1" 
      passwordStrengthRegularExpression="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=(.*\W)).{8,128}$" />
  </providers>
</membership>

The above configuration validates the password to meet the following criteria:
+ It is at least 8 characters and not exceed 128 characters
+ It contains at least 1 lower-case character
+ It contains at least 1 upper-case character
+ It contains at least 1 numeric character
+ It contains at least 1 special (non-alphanumeric) character

References

Implement Password Expiration Check in ASP.NET MVC

1. Create SkipPasswordExpirationCheckAttribute which represents to skip the Password Expiration Check

using System;

namespace Unfuddle.MvcWebApp.Web.Mvc
{
    [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class SkipPasswordExpirationCheckAttribute : Attribute
    {
    }
}

2. Create PasswordExpirationCheckAttribute attribute which inherited from AuthorizeAttribute

using System;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;

namespace Unfuddle.MvcWebApp.Web.Mvc
{
    [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class PasswordExpirationCheckAttribute : AuthorizeAttribute
    {
        private int _maxPasswordAgeInDay;

        public PasswordExpirationCheckAttribute(int maxPasswordAgeInDay = int.MinValue)
        {
            _maxPasswordAgeInDay = maxPasswordAgeInDay;
        }

        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (!filterContext.ActionDescriptor.IsDefined(typeof(SkipPasswordExpirationCheckAttribute), inherit: true)
                && !filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(SkipPasswordExpirationCheckAttribute), inherit: true))
            {
                if (_maxPasswordAgeInDay != int.MinValue)
                {
                    IPrincipal user = filterContext.HttpContext.User;

                    if (user != null && user.Identity.IsAuthenticated)
                    {
                        MembershipUser membershipUser = Membership.GetUser(user.Identity.Name);

                        if (membershipUser != null)
                        {
                            TimeSpan timeSpan = DateTime.Today - membershipUser.LastPasswordChangedDate.Date;

                            if (timeSpan.Days >= _maxPasswordAgeInDay)
                            {
                                HttpContext httpContext = HttpContext.Current;
                                HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);
                                RouteData routeData = new RouteData();
                                RequestContext requestContext = new RequestContext(httpContextBase, routeData);
                                UrlHelper urlHelper = new UrlHelper(requestContext);

                                filterContext.HttpContext.Response.Redirect(urlHelper.Action("ChangePassword", "Account", new { reason = "passwordExpired" }));
                            }
                        }
                    }
                }
            }

            base.OnAuthorization(filterContext);
        }
    }
}

3. Add a setting MaxPasswordAgeInDay in ~\Properties\Settings.settings

MaxPasswordAgeInDay in Settings
MaxPasswordAgeInDay in Settings

4. Register PasswordExpirationCheckAttribute in ~\App_Start\FilterConfig.cs

filters.Add(new PasswordExpirationCheckAttribute(Settings.Default.MaxPasswordAgeInDay));

5. Register SkipPasswordExpirationCheckAttribute in LogOn and ChangePassword action methods of the AccountController

[AllowAnonymous]
[SkipPasswordExpirationCheck]
public ActionResult LogOn()
{
	......
}

[AllowAnonymous]
[SkipPasswordExpirationCheck]
[HttpPost]
public ActionResult LogOn(LogOnViewModel model, string returnUrl)
{
	......
}

[AllowAnonymous]
[SkipPasswordExpirationCheck]
public ActionResult ChangePassword(string reason = null)
{
	......
}

[AllowAnonymous]
[SkipPasswordExpirationCheck]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordViewModel model)
{
	......
}

References

Membership in ASP.NET MVC

1. Edit ~\web.config to include the following membership configuration.

<configuration>
	<connectionStrings>
		<clear />
		<add name="ADConnectionString" connectionString="LDAP://ldap.unfuddle.com/CN=Users,DC=unfuddle,DC=com" />
	</connectionStrings>
	<system.web>
		<authentication mode="Forms">
			<forms loginUrl="~/Account/LogOn" timeout="30" />
		</authentication>
		<membership defaultProvider="ADMembershipProvider">
			<providers>
				<clear />
				<add name="ADMembershipProvider" 
					type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
					connectionStringName="ADConnectionString" connectionUsername="<domainName>\administrator" connectionPassword="password"
					applicationName="Unfuddle.MvcWebApp" />
			</providers>
		</membership>
	</system.web>
</configuration>

2. Setup Forms Authentication

3. In AccountController.LogOn action method, use Membership to perform the authentication.

public class AccountController : Controller
{
	//
	// POST: /Account/LogOn
	[AllowAnonymous]
	[HttpPost]
	public ActionResult LogOn(LogOnViewModel model, string returnUrl)
	{
		if (this.ModelState.IsValid && Membership.ValidateUser(model.UserName, model.Password))
		{
			FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
			if (this.Url.IsLocalUrl(returnUrl))
			{
				return Redirect(returnUrl);
			}
			else
			{
				return RedirectToAction("Index", "Home");
			}
		}

		// If we got this far, something failed, redisplay form
		this.ModelState.AddModelError("", "Incorrect user name or password.");
		return View(model);
	}
}

References

Forms Authentication in ASP.NET MVC

1. Edit ~\web.config to include the following forms-based authentication configuration.

<system.web>
	<authentication mode="Forms">
		<forms loginUrl="~/Account/LogOn" timeout="30" />
	</authentication>
</system.web>

2. Register AuthorizeAttribute in ~\App_Start\FilterConfig.cs.

filters.Add(new AuthorizeAttribute());

3. Add view model LogOnViewModel in ~\Models\Account\LogOnViewModel.cs.

public class LogOnViewModel
{
	[Required]
	[Display(Name = "User name")]
	public string UserName { get; set; }

	[Required]
	[DataType(DataType.Password)]
	[Display(Name = "Password")]
	public string Password { get; set; }

	[Display(Name = "Remember me?")]
	public bool RememberMe { get; set; }
}

4. Add controller AccountController and LogOn action methods for both HttpGet & HttpPost.

public class AccountController : Controller
{
	//
	// GET: /Account/LogOn
	[AllowAnonymous]
	public ActionResult LogOn()
	{
		LogOnViewModel model = new LogOnViewModel();

		return View(model);
	}

	//
	// POST: /Account/LogOn
	[AllowAnonymous]
	[HttpPost]
	public ActionResult LogOn(LogOnViewModel model, string returnUrl)
	{
		if (this.ModelState.IsValid && Membership.ValidateUser(model.UserName, model.Password))
		{
			FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
			if (this.Url.IsLocalUrl(returnUrl))
			{
				return Redirect(returnUrl);
			}
			else
			{
				return RedirectToAction("Index", "Home");
			}
		}

		// If we got this far, something failed, redisplay form
		this.ModelState.AddModelError("", "Incorrect user name or password.");
		return View(model);
	}

	//
	// POST: /Account/LogOff
	[HttpPost]
	public ActionResult LogOff()
	{
		FormsAuthentication.SignOut();

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

5. Add view in ~\Views\Account\LogOn.cshtml.

@model SecurityApp.Models.Account.LogOnViewModel
@{
    Layout = null;
    ViewBag.Title = "Log On";

    ViewBag.ReturnUrl = Request["ReturnUrl"];
}
<!DOCTYPE html>
<html>
......
<body>
    <h2>@ViewBag.Title</h2>
    @using (Html.BeginForm(null, null, new { returnUrl = ViewBag.ReturnUrl }, FormMethod.Post))
    {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)<br />
        @Html.TextBoxFor(m => m.UserName, new { placeholder = Html.DisplayNameFor(m => m.UserName) })<br />
        @Html.PasswordFor(m => m.Password, new { placeholder = Html.DisplayNameFor(m => m.Password) })<br />
        @Html.CheckBoxFor(m => m.RememberMe)
        @Html.DisplayNameFor(m => m.RememberMe)<br />
        <button type="submit">Log On</button>
    }

    ......
</body>
</html>

6. In ~\Views\Shared\_Layout.cshtml, add a HTML form to handle log off.

@using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logOffForm" }))
{
	@Html.AntiForgeryToken()
}

7. In ~\Views\Shared\_Layout.cshtml, add a hyperlink to log off.

<a href="javascript:$('#logOffForm').submit()">Log Off</a>

References