ASP.NET Identity with webforms

Towards the end of last week ASP.NET Identity 2.0 was released. ASP.NET Identity has been developed with the following goals:

  • To provide a single framework that will work with all of the ASP.NET frameworks, such as ASP.NET MVC, Web Forms, Web Pages, Web API, and SignalR.
  • To give the user control over the schema of user and profile information.
  • To allow users to write unit test against parts of the application that use ASP.NET Identity.
  • To make it OWIN compatible. ASP.NET Identity uses OWIN authentication for managing users of a web application. This means that instead of using FormsAuthentication to generate the cookie, the application uses OWIN CookieAuthentication.
  • To support claims-based authentication, which allows developers to be a lot more expressive in describing a user’s identity than membership roles.
  • To allow user to be authenticated using their social log-ins such as using Microsoft Account, Facebook, Twitter, Google, and others.
  • And to enable ASP.NET team to have rapid release cycles, by making ASP.NET Identity available as a NuGet package.

With all these design goals and features, it makes Identity 2.0 to be much more mature for today’s web than Membership providers. Lets take a look on how we can integrate Identity 2.0 with an ASP.NET webform application.

In VS 2013 create an ASP.NET web application by selecting the “Empty” template.

Project template

Notice the “Change Authentication” button is disabled and no authentication support is provided in this template. It’s ok, we will add our own. Next lets install the required packages using nuget. We need ASP.NET Identity CoreASP.NET Identity EntityFramework and ASP.NET Identity Owin in our project.

PM> Install-Package Microsoft.AspNet.Identity.Core
PM> Install-Package Microsoft.AspNet.Identity.EntityFramework
PM> Install-Package Microsoft.AspNet.Identity.Owin 

ASP.NET Identity uses entity framework to store the user information and it uses OWIN authentication middleware that uses the OWIN API. Lets proceed by adding three webform “Login.aspx” to allow user to login, “Register.aspx” to allow users to register themselves with our application and “Home.aspx”. For the time time being we will keep our requirements simple, our “Home.aspx” page will be only available to the logged-in users. Add these settings to the web.config :

 <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\WebFormsIdentity.mdf;Initial Catalog=WebFormsIdentity;Integrated Security=True"
 providerName="System.Data.SqlClient" />
 </connectionStrings>

 <system.web>
    <authorization>
       <deny users="?"/>
    </authorization>
    <authentication mode="None">
    </authentication>
 </system.web>
 <location path="Login.aspx">
    <system.web>
       <authorization>
          <allow users="*"/>
       </authorization>
    </system.web>
 </location>
 <location path="Register.aspx">
    <system.web>
       <authorization>
          <allow users="*"/>
       </authorization>
    </system.web>
 </location>

The connection string entry for the database, will be used to store user information. The database will be created at runtime by EntityFramework for the Identity entities. Now add an OWIN startup class.

OWIN Startup Class

To the “Configuration()” we make these changes :

 public void Configuration(IAppBuilder app)
 {
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
       AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
       LoginPath = new PathString("/Login.aspx")
    });
 }

The class name “Startup” and the method “Configuration” with an “IAppBuilder” input parameter is a convention that Katana looks out for and uses to configure the MiddleWare modules. We are all well aware that Forms authentication, used a cookie to represent the current logged in user. Upon subsequent requests from the user, Forms authentication would validate the user with the help of the cookie. Now, with the introduction of OWIN authentication middleware, the new cookie-based implementation is called the OWIN cookie authentication middleware. This performs the same task — it can issue a cookie and then validates the cookie on subsequent requests, with one improvement that it is claims-aware.

The “UseCookieAuthentication()” method adds a cookie-based authentication middleware to our web application pipeline. To which we pass a “CookieAuthenticationOptions” object consisting of various option to be used by the authentication middleware. “AuthenticationType” in the options corresponds to the Identity AuthenticationType property, over here we have set it to “DefaultAuthenticationTypes.ApplicationCookie” which is the default value. If we are to be allowing logging in with third party login providers such as using Microsoft/Google/Facebook/Twitter accounts then we are to use “DefaultAuthenticationTypes.ExternalCookie” (I will explain this in future post).

The “LoginPath” option informs the middleware to change an outgoing “401” unauthorized status code into a “302” redirection onto the given login path. The current url which generated the “401” is added to the “LoginPath” as a query string parameter named by the “ReturnUrl”. After successfully authentication, the “ReturnUrl” value is used to redirect the browser back to the url which caused the original unauthorized status code. On absence of “LoginPath”, the middleware will not look for “401” unauthorized status codes, and it will not redirect automatically when a login occurs.

The login page will have this inside the form tag :

 <h4 style="font-size: medium">Log In</h4>
 <hr />
 <div>
    <asp:Literal runat="server" ID="litErrorMsg" Text="Invalid username or password." Visible="false" />
 </div>
 <div style="margin-bottom: 10px">
    <asp:Label runat="server" AssociatedControlID="txtbxUserName">User name</asp:Label>
    <br />
    <asp:TextBox runat="server" ID="txtbxUserName" />
 </div>
 <div style="margin-bottom: 10px">
    <asp:Label runat="server" AssociatedControlID="txtbxPassword">Password</asp:Label>
    <br />
    <asp:TextBox runat="server" ID="txtbxPassword" TextMode="Password" />
 </div>
 <div style="margin-bottom: 10px">
    <asp:Button ID="btnSignIn" runat="server" Text="Log in" OnClick="btnSignIn_Click" />
    <a href="Register.aspx">New User</a>
 </div>

And the code behind we have this :

namespace IdentityDemo
{
   public partial class Login : System.Web.UI.Page
   {
      protected void btnSignIn_Click(object sender, EventArgs e)
      {
         var userStore = new UserStore<IdentityUser>();
         var userManager = new UserManager<IdentityUser>(userStore);
         var user = userManager.Find(txtbxUserName.Text, txtbxPassword.Text);

         if (user != null)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);

            authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, userIdentity);

            if (Request.QueryString["ReturnUrl"] == null)
               Response.Redirect("~/Secured/Home.aspx");
         }
         else
         {
            litErrorMsg.Visible = true;
         }
     }
  }
}

The “UserStore” class is the default EntityFramework implementation of a user store. This class implements the ASP.NET Identity Core’s minimal interfaces: “IUserStore”, “IUserLoginStore”, “IUserClaimStore” and “IUserRoleStore”. The “IdentityUser” class is the default EntityFramework implementation of the “IUser” interface. The “UserManager” class exposes user related APIs which will automatically save changes to the “UserStore”. Such as the “Find()” method accepts the user provided username and password and returns “IdentityUser” on match. After getting the user, we create a claims-based identity for the user, by use of “CreateIdentity()” method. The call to “SigIn()” method using the “AuthenticationManager”, matches the authentication type to the corresponding authentication middleware mentioned in the “ConfigureAuth” initialization code. Since we have mentioned the cookie authentication middleware, a cookie is issued that contains the claims. Thus call to “SigIn()” does the same job as that of “FormAuthentication.SetAuthCookie()” used by the “FormsAuthentication” module. Using “AuthenticationProperties” object we can pass additional options while claim based cookie creation, over here “IsPersistent” option indicates if the cookie is to be persistent or not.

The register page looks like this :

 <h4 style="font-size: medium">Register a new user</h4>
 <hr />
 <p>
    <asp:Literal runat="server" ID="litStatusMessage" />
 </p>
 <div style="margin-bottom: 10px">
    <asp:Label runat="server" AssociatedControlID="txtbxUserName">User name</asp:Label>
    <br />
    <asp:TextBox runat="server" ID="txtbxUserName" />
 </div>
 <div style="margin-bottom: 10px">
    <asp:Label runat="server" AssociatedControlID="txtbxPassword">Password</asp:Label>
    <br />
    <asp:TextBox runat="server" ID="txtbxPassword" TextMode="Password" />
 </div>
 <div style="margin-bottom: 10px">
    <asp:Label runat="server" AssociatedControlID="txtbxConfirmPassword">Confirm password</asp:Label>
    <br />
    <asp:TextBox runat="server" ID="txtbxConfirmPassword" TextMode="Password" />
 </div>
 <div>
    <asp:Button ID="btnRegister" runat="server" Text="Register" OnClick="btnRegister_Click" />
 </div>
namespace IdentityDemo
{
   public partial class Register : System.Web.UI.Page
   {
      protected void btnRegister_Click(object sender, EventArgs e)
      {
         // Default UserStore constructor uses the default connection string named: DefaultConnection
         var userStore = new UserStore<IdentityUser>();
         var manager = new UserManager<IdentityUser>(userStore);
         var user = new IdentityUser() { UserName = txtbxUserName.Text };         IdentityResult result = manager.Create(user, txtbxPassword.Text);         if (result.Succeeded)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
            authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity);
            Response.Redirect("~/Home.aspx");
         }
         else
         {
            litStatusMessage.Text = result.Errors.FirstOrDefault();
         }
      }
   }
}

In order to register a new user we make a call to “Create()” method, which creates the user and associates the password with the user. After user creation we create claim based cookie for the registered user. The “Home.aspx” page shows a welcome message and a logout button.

<asp:Literal runat="server" ID="litStatus" />
<br />
<asp:Button ID="btnLogOut" runat="server" Text="Log out" OnClick="btnLogOut_Click" />
namespace IdentityDemo.Secured
{
   public partial class Home : System.Web.UI.Page
   {
      protected void Page_Load(object sender, EventArgs e)
      {
         litStatus.Text = string.Format("Hello {0} !!", User.Identity.GetUserName());
      }      protected void btnLogOut_Click(object sender, EventArgs e)
      {
         var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
         authenticationManager.SignOut();
         Response.Redirect("~/Login.aspx");
      }
   }
}

The “SignOut()” method does the same job as that of “FormsAuthentication.SignOut()” method used by the “FormsAuthentication” module, by deleting the claims based cookie generated for the user.

In my next post, we will see how we can enable two factor authentication for the user accounts using Identity.

You can get the code files from here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s