Integrating Identity Framework (Individual User Accounts)

When we select Individual User Accounts as Authentication Method while creating a new project (MVC Framework in this case) we have Identity Framework added to our solution with a basic default configuration.

Here is a list of the main components that are added:

  • AccountController: it handles the user’s registration, login and logout. It includes as well user’s validation and complementary workflows, such as the Reset Password workflow and the Forget Password workflow. Is a sample controller with the most common configuration that can be customized for our needing.
  • AccountViewModels/Account Views: view models and views for the AccountController.
  • ManageController: it handles the user management pages in our MVC Framework application. Is complementary with the AccountController. Is a sample controller with the most common configuration that can be customized for our needing.
  • ManageViewModels/Manage Views: view models and views for the ManageController.
  • IdentityModels: here we can find the Identity EF Context. It works as for a normal EF Code First application. The ApplicationUser class can be extended to add custom properties that we want to store for our users.

The IdentityConfig.cs and Startup.Auth.cs files are added as well under the App_Start folder. This files contain many classes that can be used to configure many behaviors, such as the email/SMS services to send email and SMS, the password strength requirements, the cookie authentication policy and so on.

Identity Framework uses OWIN (Open Web Interface for .Net) to decouple our application from the web server. This allow the application to work with different configurations of OS + WebServer, without being tied to Windows and IIS.


Setting the target DB for Identity Framework Data

As for any other EF Code first application we can do this by setting the connection string when we create the context:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base(@"Data Source=.\SQLEXPRESS;Initial Catalog=TestIdentityDB;Integrated Security=True", throwIfV1Schema: false)
    {
    }
 
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

At the first attempt to access the DB this will be created (as for any normal EF Code First application). Here is its general structure:

The tables are all empty. If we try to register a new user a record will be created into the AspNetUsers table. Here is how it looks like:


Setting an action to require authentication

We can use the Authorize attribute to specify an action that requires authentication. An user that isn’t logged in will be sent to the login page if he tries to access the action.

[Authorize]
public ActionResult AuthenticatedPage()
{
    return View();
}

From the view is always possible to access the current user’s information through the User.Identity object:

@using Microsoft.AspNet.Identity
 
@{
    ViewBag.Title = "AuthenticatedPage";
}
 
<h2>AuthenticatedPage</h2>
<h3>Welcome @User.Identity.GetUserName()</h3>

Setting an action to not require authentication

The Authorize attribute can be set also at controller-level. This means that all the controller’s actions will inherit the attribute without needing a specific configuration. In this scenario where the authentication is required as default behavior we can use the AllowAnonymous attribute to specify that an action shouldn’t require authentication.

[Authorize]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
 
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";
 
        return View();
    }
 
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
 
        return View();
    }
 
    [AllowAnonymous]
    public ActionResult AuthenticatedPage()
    {
        return View();
    }
}

Setting an action to require role-based authentication

In addition to just requiring our user to be somehow authenticated in order to access a certain action, we can also require that the user is part of some authorization group (administrators for instance). In order to do this we need, first of all, to have users associated with roles into our Identity database. This can be easily done through a custom EF Initializer that will seed our Code First database with some basic user and role:

public class IdentityInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
    protected override void Seed(ApplicationDbContext context)
    {
        var passwordHasher = new PasswordHasher();
 
        var adminRole = new IdentityRole("Admin");
        var testRole = new IdentityRole("Test");
 
        var adminUser = new ApplicationUser
        {
            UserName = "admin@admin.com",
            Email = "admin@admin.com",
            PasswordHash = passwordHasher.HashPassword("admin"),
            SecurityStamp = Guid.NewGuid().ToString()
        };
        adminUser.Roles.Add(new IdentityUserRole
        {
            UserId = adminUser.Id,
            RoleId = adminRole.Id
        });
 
        var testUser = new ApplicationUser
        {
            UserName = "test@test.com",
            Email = "test@test.com",
            PasswordHash = passwordHasher.HashPassword("test"),
            SecurityStamp = Guid.NewGuid().ToString()
        };
        testUser.Roles.Add(new IdentityUserRole
        {
            UserId = testUser.Id,
            RoleId = testRole.Id
        });
 
        //Creates Roles
        context.Roles.Add(adminRole);
        context.Roles.Add(testRole);
 
        //Creates Users
        context.Users.Add(adminUser);
        context.Users.Add(testUser);
    }
}

Here is what the code above will insert into the database:

Now that our users are linked to roles we can specify, for instance, an action that will be accessible only for users in the “Admin” group. An user that isn’t part of that group will be sent to the login page if he tries to access the action. We can do this in a very easy way by passing some argument to the Authorize attribute.

[Authorize(Roles = "Admin")]
public ActionResult AuthenticatedPage()
{
    return View();
}
Advertisements

Setting a different Web.config for every publish environment (Config Transformation)

First of all, since a VS project comes with just 2 build configurations (Debug and Release), we need to create a new build configuration for Staging. In this way we’ll be able to have a Web.config transformation for it as well.

We can add the new build configuration from the Configuration Manager:

Now that we have all the build configurations we need we can create a config transformation for our new Staging build configuration (we should already have the one for Debug and Release) by right-clicking on our Web.config and selecting “Add Config Transform”. A new Web.Staging.config file will be added under the Web.config in the solution.


Working with Config Transformation files

We can use the config transformation files to specify transformations to apply to the original Web.config in order to obtain any configuration-related Web.config. Since the original Web.config is often related to a Development environment we normally don’t have any transformation to apply for the Debug build configuration (we just use the original Web.config). So what we basically do is to define transformations for the Staging and Release build configurations by replacing any configuration key that need to be different between the environments.


Updating a Connection String

A thing that is often useful to change between different environments is the value of connection strings, to be able to connect to a different DB based on the environment we are publishing to. Here is an example configuration in our original Web.config:

<connectionStrings>
  <add name="ConString" connectionString="myDebugCS" />
</connectionStrings>

And here is what we need to write in our Web.Staging.config transformation file to change its value for our Staging environment:

<connectionStrings>
  <add name="ConString" connectionString="MyConStr"
        xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>

What we are doing with the new “xdt” attributes is to tell that we want new attributes values for the item who match the “name” attribute.
As result when we’ll publish under Staging build configuration our “connectionString” property for the “ConString” connection string will be replaced with the “MyStagingCS” value.


Updating an AppSetting configuration

<add key="MyConfigurationKey" value="MyConfigurationValue" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"></add>

Adding an AppSetting configuration

<add key="MyConfigurationKey" value="MyConfigurationValue" xdt:Transform="Insert"></add>

Removing an AppSetting configuration

<add key="MyConfigurationKey" xdt:Transform="Remove" xdt:Locator="Match(key)"></add>

Replacing a node with all its subnodes

In this case we want to replace the whole node and its content (so the inner “network” node as well) for the smtp configuration whose “from” attribute is “target-email@my-company.com”.

<smtp deliveryMethod="Network" from="target-email@my-company.com" xdt:Transform="Replace" xdt:Locator="Match(from)">
  <network host="my-company.host.com" port="25"></network>
</smtp>

Replacing a node based on its subnodes

This is something that can happen if we have to replace something about assembly bindings, which is defined by a list of nodes like this:

<dependentAssembly>
  <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
  <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>

Let’s imagine that we want to replace the “dependantAssembly” configuration node for the “System.Web.Helpers” assembly. Unfortunately
this information is written inside the “assemblyIdentity” inner node, so we have to specify a more complex rule:

<dependentAssembly xdt:Transform="Replace"
                   xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Web.Helpers')">
  <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"></assemblyIdentity>
  <bindingRedirect newVersion="3.0.0.0" oldVersion="0.0.0.0-3.0.0.0"></bindingRedirect>
</dependentAssembly>