CS 461

Using ASP.NET Identity


Initial Setup

Begin by creating a new MVC project and selecting “Individual User Accounts”. Run the blank project and use it to create at least one account. This will get you a LocalDB instance that you can use for local testing. With this starting code you get one DbContext class for the Identity system to use, as well as a model class for your user, a bunch of view models and configuration code.

Next up is to swipe the database schema that Identity uses. Use Server Explorer, SQL Server Object Explorer or SQL Server Management Studio to connect to the local database instance and copy the SQL code for the tables. Delete the part for creating the __MigrationHistory table. Save this code in a .sql file in your App_Data folder. While you're at it, write another .sql file to drop all database tables. Now you have the ability to create the the necessary database locally or on Azure.

We're definitely not going to use Code First with Database Migrations, so let's disable migrations. ASP.NET Identity uses Code First with migrations by default. Delete the migrations folder if it exists and then add this line to the ApplicationDbContext constructor. Included in this example, and commented out, is the code for switching connection strings. Choose which one you want depending on whether you're testing locally or using an Azure database.

    public ApplicationDbContext()
    : base("DefaultConnection", throwIfV1Schema: false)
  //: base("AzureConnection", throwIfV1Schema: false)
    {
        // Disable code-first migrations
        Database.SetInitializer<ApplicationDbContext>(null);
    }

Create a database in the cloud on Azure. Copy the connection string and put it in your Web.config. Since there's a password in there you don't want to have that file in your repo on the web, so move it to a separate file that isn't added to your Git repo, i.e. use an external configuration file. Your connection string should look something like this:

<!-- in Web.config, my external file is Web.Azure.config-->
<connectionStrings configSource="Web.Azure.config"></connectionStrings>

You'll have 2 connection strings in this separate file at the moment: one for your default connection to the localDB and one for Azure. You can switch back and forth by changing the ApplicationDbContext base constructor call (above).

Strategies

Here are a few strategies you can take from this point for your software design. The following order goes from easiest to hardest. Pros and cons follow in the details sections.

  1. Keep everything separate. Make no changes to the Identity database (i.e. don't add any columns to the AspNetUsers table) and do all your application specific work just like you've been doing all along, with a separate DbContext class and a separate connection string (even if it's to the same database).
  2. Modify only a bit. Make a few small changes to the Identity database but essentially keep it separate from your application. This means adding columns to Identity tables but not forming any relations between the Identity tables and your own. For example, you might add a pseudonym, or zip code, or some other simple user attributes. You'll still have a separate database context and connection string.
  3. Integrate everything. Make changes to the Identity database and form relations between it and your tables, and vice versa. With this model you can use a single database context class and connection string. Sure, you can always use a separate database but you don't have to.

Let's look at each one in some depth

1. Keep everything separate

So you have your Identity database set up from the template code, an ApplicationDbContext that inherits from IdentityDbContext<ApplicationUser> and a connection string lets call AzureAuthConnection. What else do you have? For everyday use, you have a UserManager, which is your primary goto for, uh, managing users. Specifically you get an ApplicationUserManager which is-a UserManager<ApplicationUser>. Look in AccountController for how to get one:

HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>()

You need to be in, or have access to, a Controller to use this as HttpContext is a property in the controller class. What can you do with one? Think get, set and find. Things like email, username, password, role, ... In a Controller you also have access (from the base class) to the User property, which gives you access to the Identity system where you can ask questions like, is the current user logged in? Here are some simple use cases. All assume you are in a controller (or have access to one). These are all synchronous methods, but there are async versions for all these as well.

Is the current requestor/user logged in?

if(User.Identity.IsAuthenticated)
{
    // only if you need to ask manually.  Normally use [Authorize] attribute on the class or method.
}

What is the Id of the current authorized user?

string id = User.Identity.GetUserId();

Get the user object for the current authorized user

ApplicationUser currentUser = UserManager.FindById(id);

Find a user by email

currentUser = UserManager.FindById("thor@gmail.com");

Find a user by username

currentUser = UserManager.FindByName("thor");

You can use Entity Framework to go directly to the users table:

Print out to debug info about all users

ApplicationDbContext db = new ApplicationDbContext();
Debug.WriteLine("\tAll items in db.Users (i.e. AspNetUsers)");
foreach(var item in db.Users.ToList())
{
    Debug.WriteLine(string.Format("\t{0} {1} Logins: {2}", item.Id, item.UserName, item.Logins.Count));       
}
So how do you design things? Do all your normal design and keep it completely separate from the Identity tables. Then create, or add to, an entity that has an attribute for the user ID that Identity holds. Something like this:
-- My app is called SharedSchedule
CREATE TABLE [dbo].[ScheduleUsers]
(
    [ID]               INT IDENTITY (1,1) NOT NULL,
    [FirstName]        NVARCHAR (50) NOT NULL,
    [LastName]         NVARCHAR (50) NOT NULL,
    [ASPNetIdentityID] NVARCHAR (128),
    CONSTRAINT [PK_dbo.ScheduleUser] PRIMARY KEY CLUSTERED ([ID] ASC)
    -- NO foreign key constraint on ASPIdentityID
);

Reverse engineer your models as usual using Entity Framework ORM (but don't do that for any of the ASP.NET Identity tables). This will give you a new DbContext class and a separate connection string. Go through that when accessing your data:

[Authorize]
public ActionResult SomeActionMethod()
{
    // Get the ASP.NET Identity Id of the currently authorized user
    string id = User.Identity.GetUserId();
    MyOtherDbContext db = new MyOtherDbContext();
    ScheduleUser user = db.ScheduleUsers.Where( u => u.ASPNetIdentityID == id).FirstOrDefault();
    // use this object normally; assumes you set the ASPNetIdentityID when the user was created
    // or at some point later
}

So to summarize, store the ASP.NET Identity created Id separately in one of your tables; find by that Id and then you're off and running.

2. Modify only a bit

You might want to add (Note: don't remove anything) something to one of the Identity tables if you find that you need it regularly. For example, you may want to welcome the user with a pseudonym (not in the tables) rather than their email or username (both of which are there). Or if you're constantly using their location, or ... It should be something that you need frequently and don't want to look up in your own tables (by ID as the previous example showed). You can also store an ID in the Identity tables; an ID of one of your own user tables perhaps. You could then use that to look up a user in your own tables with a Find (since it would likely be a primary key).

So how do you get your data in to these Identity tables? You can find examples on the Web but they seem to all use Code-First with Database Migrations. Our strategy is to do the same thing but instead of running a database update (migration) we do the update to the database ourselves.

Step 1: add something to the user class that Identity uses:

// In IdentityModels.cs
// This class maps to AspNetUsers
public class ApplicationUser : IdentityUser
{
   // Existing code
   
   // Our custom addition
   [Display(Name="Nickname")]
   [StringLength(256, MinimumLength = 1)]
   public string Pseudonym { get; set; }
}

Identity uses Entity Framework by default so this addition will cause the EF ORM to map ApplicationUser.Pseudonym to a column in AspNetUsers named “Pseudonym”. But it doesn't yet exist in the database. Well, that's easy to fix: add that to your db script that creates the Identity tables

CREATE TABLE [dbo].[AspNetUsers]
(
   [Id]         NVARCHAR (128) NOT NULL,
   [Email]      NVARCHAR (256) NULL,
   --...
   [Pseudonym]  NVARCHAR (256) NULL,
   CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC)
);

and then drop and create your db. Or, for something this simple you can (additionally! not in place of) write an ALTER TABLE statement and run that to alter the database to match what your new script says. Creating a statement to alter the table to its new configuration is in fact precisely one of the things that migrations do. In fact here's the SQL it generates for exactly this addition:

ALTER TABLE [dbo].[AspNetUsers] ADD [Pseudonym] [nvarchar](256);

Now you can give that attribute a value (e.g. when the user registers) and then retrieve it whenever you like from an ApplicationUser object. In the default setup ApplicationUser maps to the AspNetUsers table.

public class HomeController : Controller
{
    public ActionResult Index()
    {
       if(User.Identity.IsAuthenticated)
       {
           ApplicationUser currentUser = UserManager.FindById(User.Identity.GetUserId());
           ViewBag.Pseudonym = currentUser.Pseudonym ?? "";
       }
       //...
    }
}

As with #1 above, with this model you do not generate model classes for the Identity tables. You'll use a separate DbContext class for your own tables.

3. Integrate everything

So you're going to roll the dice and integrate everything with one context? Ok, let's roll. In this example we will create a new table that has a foreign key to the Identity table's Id, and we'll use the same context as Identity. We'll have a navigation property from ApplicationUser to this new table as well as a navigation property from the new table to ApplicationUser. Here's the new table. We start by defining the database, as is usual with the database-first approach. I'll make this one different than in #1 to keep them easily separated.

-- In the same script as AspNetUsers
CREATE TABLE [dbo].[MyUsers]
(
    [ID]         INT IDENTITY (1,1) NOT NULL,
    [FirstName]  NVARCHAR (128) NOT NULL,
    [LastName]   NVARCHAR (128) NOT NULL,
    [IdentityID] NVARCHAR (128),
    CONSTRAINT [PK_dbo.MyUsers] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.MyUsers_dbo.AspNetUsers_Id] FOREIGN KEY ([IdentityID]) 
            REFERENCES [dbo].[AspNetUsers] ([Id])
);

Here we have a one to many relationship between AspNetUsers and MyUsers. If both entities are unique a 1:1 relationship would be more realistic. Let's assume this one is more general in that a logged in and authorized user might, for some reason, want to or need to have zero or multiple MyUsers.

We can execute this script and create the database. But our application won't know anything about it until we reverse engineer (or simply write -- it isn't that hard for one class) the model class and tell it to use this relationship properly. Reverse engineering the model isn't difficult, just make sure and exclude the AspNet tables -- those are already taken care of by the Identity system. We just want our class and the changes that are needed to the context class.

So here's what we do. Reverse engineer the model and use a throw-away name for the new context that get's generated. Throw away that context class as well as the connection string that has just been generated. Before you throw away the context class take a look at it to see if there's anything interesting in there. (Actually you don't have to throw it away, just don't use it.)

At this point we'll get a model class:

// MyUser.cs
public partial class MyUser
{
   public int ID { get; set; }
   
   [Required]
   [StringLength(128)]
   public string FirstName { get; set; }
   
   [Required]
   [StringLength(128)]
   public string LastName { get; set; }
   
   [StringLength(128)]
   public string IdentityID { get; set; }
   
   // But you won't get this: the navigation property
   // So add it.  It's what you want!
   public virtual ApplicationUser ApplicationUser { get; set; }
}

The ORM won't give you the navigation property. I'm not sure if this is because we didn't import the AspNetUsers table (probably) or if it's because the name is remapped somewhere within Identity (possibly?), but either way you won't get it, but you need it, so add it. That's for the 1 side of the 1:N relationship, i.e. we're at the many and we're pointing at the 1 so we get a single entity rather than a list, which is what we'll have on the other side.

BTW: Don't call this entity User (with an associated table called Users) because that already exists in Identity (see above).

Now let's add the other side navigation property.

// In IdentityModels.cs
public class ApplicationUser : IdentityUser
{
   // Add a constructor to initialize the navigation automatic property
   public ApplicationUser()
   {
       MyUsers = new HashSet<MyUser>();
   }
   
    // The existing part of the class
    
    // Add the navigation property to every MyUser that has this objects ID
    public virtual ICollection<MyUser> MyUsers { get; set; }
}

Unfortunately that's not quite it. Since we're not using the context class that EF created for us we need to update the Identity version of the context.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    // Constructor and Create method...
    
    // Add a DbSet for our new table so we can access all users 
    public virtual DbSet<MyUser> MyUsers { get; set; }
    
    // Add the method used to configure EF mapping
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Invoke the Identity version of this method to configure relationships 
        // in the AspNetIdentity models/tables
        base.OnModelCreating(modelBuilder);
        
        // Add a configuration for our new table.  Choose one end of the relationship
        // and tell it how it's supposed to work
        modelBuilder.Entity<ApplicationUser>()
            .HasMany(e => e.MyUsers)        // ApplicationUser has many MyUser
            .WithOptional(e => e.ApplicationUser) // allowed nulls in table
            .HasForeignKey(e => e.IdentityID);  // MyUser includes this specified foreign key
                                                // for an ApplicationUser
    }
}

Finally we can use it in a controller.

// In a Controller and you already have a UserManager
// User has been authenticated and is authorized
// Get the current AspNetUser
ApplicationUser currentUser = UserManager.FindById(User.Identity.GetUserId());
// Follow the navigation property to print out all MyUsers (recall we had this as 1:N)
Debug.WriteLine("\tItems in currentUser.MyUsers.:");
int savedID = 0;
foreach( var item in currentUser.MyUsers.ToList())
{
    Debug.WriteLine(string.Format("\t\t{0} {1} {2} {3}", item.ID, item.FirstName, item.LastName, item.IdentityID));
    savedID = item.ID;  // so we can use it below for convenience
}

// Now go the other way, start from a MyUser and go to it's AspNetUser
ApplicationDbContext db = new ApplicationDbContext();
MyUser u = db.MyUsers.Find(savedID);
ApplicationUser au = u.ApplicationUser;        // follow the nav property
Debug.WriteLine("\t\t{0} {1} -> {2} {3}", u.FirstName, u.LastName, au.Email, au.Id);

// And to show we can access everything:
Debug.WriteLine("\tAll items in db.MyUsers");
foreach (var item in db.MyUsers.ToList())
{
    Debug.WriteLine(string.Format("\t\t{0} {1} {2}",item.FirstName, item.LastName, item.IdentityID));
}
Debug.WriteLine("\tAll items in db.Users");
foreach (var item in db.Users.ToList())
{
    Debug.WriteLine(string.Format("\t\t{0} {1} Logins: {2}", item.Id, item.Pseudonym, item.Logins.Count));
}

To get the new MyUser into the database with the relation set correctly I had to modify RegisterViewModel to add in the first and last names, then edit the View to add those text fields, and finally the account controller to create the objects and save to the database:

// In AccountController
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email, Pseudonym = model.Pseudonym };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

            // >>>>>ADDED: Create MyUser object and save to db
            ApplicationDbContext db = new ApplicationDbContext();
            MyUser myuser = new MyUser { FirstName = model.FirstName, LastName = model.LastName, IdentityID = user.Id};
            db.MyUsers.Add(myuser);
            db.SaveChanges();
            
            return RedirectToAction("Index", "Home");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Now you can continue to build out your models. Just remember that when you generate your models you must manually update the ApplicationDbContext class yourself.