In this article, we are going to learn about extending IdentityUser with custom properties in ASP.NET Core Identity. 

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Extending IdentityUser

We will start with a default project that already implements identity. To learn more about ASP.NET Core Identity implementation, check out our article series on the topic.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To add our custom properties to the default IdentityUser, let’s create a class that inherits from it:

public class ApplicationUser : IdentityUser
{
}

From now on, we should use ApplicationUser everywhere in our code instead of IdentityUser.

If we take a look at the IdentityUser implementation, we can see that it inherits from the generic IdentityUser<TKey> class and uses string as the type parameter. If we inspect its primary constructor we will see that it uses a Guid as Id:

public IdentityUser()
{
    Id = Guid.NewGuid().ToString();
    SecurityStamp = Guid.NewGuid().ToString();
}

Changing the Primary Key Type on IdentityUser

Now, let’s change the key type to a real Guid so that in the SQL table generated, it can be of type uniqueidentifier, ensuring uniqueness at the database level.

To change the type of the Id property, let’s make our ApplicationUser class inherit from the generic IdentityUser<TKey> and pass the Guid as the type parameter:

public class ApplicationUser : IdentityUser<Guid>
{
}

This is the only thing that we have to change in our domain model, however, we are not ready yet.

Updating the Infrastructure Code

Once that is done, we should update the ApplicationDbContext and the dependency injection registration to be able to utilize our ApplicationUser class.

Firstly, if we inspect the IdentityDbContext class, we will notice that it has many generic parameters. It’s important to note, that by changing the primary key type of the ApplicationUser, we essentially have to change all other identity-related primary key types too.

Let’s inspect the IdentityDbContext class:

public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<TUser, TRole, TKey, 
                               IdentityUserClaim<TKey>, IdentityUserRole<TKey>, IdentityUserLogin<TKey>, 
                               IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
    where TUser : IdentityUser<TKey>
    where TRole : IdentityRole<TKey>
    where TKey : IEquatable<TKey>
{
    public IdentityDbContext(DbContextOptions options) : base(options) { }
    protected IdentityDbContext() { }
}

It has three generic parameters, the first two are the TUser and TRole parameters and the third is the TKey parameter. From the generic parameter constraints we can see, that both TUser and TRole types must have the same TKey type. Moreover, all the other identity-related classes, such as IdentityUserClaim<TKey> or IdentityUserRole<TKey> will use the same TKey as our ApplicationUser class. So all in all, changing the ApplicationUser class to have a Guid id we made everything else have a Guid id too.

Now, let’s change our ApplicationDbContext to inherit from the correct IdentityDbContext:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

We pass the ApplicationUser class as the TUser parameter. Because we don’t plan on extending roles the way we did with users, we pass the default IdentityRole class with Guid type parameter as TRole. And finally, we specify TKey as Guid too.

As the last step, let’s configure the dependency injection registration of ASP.NET Core Identity:

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

We change the type parameter of the AddDefaultIdentity() method to use our ApplicationUser class. This way, UserManager will be able to work with our custom user class.

Adding and Configuring Custom Properties to IdentityUser

With the infrastructure configured, let’s proceed by adding some custom properties to our user class.

From now on, adding and configuring custom properties is almost the same process as when we add them to any other entity. Let’s start with some primitive properties.

Custom Primitive Properties on IdentityUser

Let’s add two primitive custom properties to the ApplicationUser class:

public class ApplicationUser : IdentityUser<Guid>
{
    public string DisplayName { get; set; }
    public DateTime LastLoginDateTime { get; set; }
}

Here, we add the DisplayName and the LastLoginDateTime properties to our class. Now, let’s see how to apply database constraints to the DisplayName property.

Let’s override the OnModelCreating() method of the ApplicationDbContext:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.Entity<ApplicationUser>(b =>
    {
        b.Property(u => u.DisplayName).IsRequired().HasMaxLength(100);
    });
}

We should make sure not to forget to call the base method as it will configure the rest of the identity framework’s database. Then we can add our configuration as usual.

We configure DisplayName to be required and be at most 100 characters long. We could configure constraints on properties of the IdentityUser class too, for example, introduce length constraints for UserName or change column names.

Custom Navigation Properties on IdentityUser

Now, let’s introduce a more complex scenario, where we’d like to extend ApplicationUser with related entities.

Let’s imagine that users can create posts on their page and each post can consist of a title and some text content.

Now, in order to bring this concept to life, let’s proceed by creating a Post entity:

public class Post
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Text { get; set; }
}

To continue, let’s configure this entity in the OnModelCreating() method:

builder.Entity<Post>(b =>
{
    b.HasKey(e => e.Id);
    b.Property(e => e.Title).IsRequired().HasMaxLength(256);
    b.Property(e => e.Text).IsRequired();
});

We inform Entity Framework about the key property, mark everything required and set the max length of the Title property to 256 characters.

Next, let’s add a Post collection to our ApplicationUser:

public class ApplicationUser : IdentityUser<Guid>
{
    public string DisplayName { get; set; }
    public DateTime LastLoginDateTime { get; set; }
    public List<Post> Posts { get; set; }
}

Once we have the Posts property added, let’s configure the relationship in the OnModelCreating() method:

builder.Entity<ApplicationUser>(b =>
{
    b.Property(u => u.DisplayName).IsRequired().HasMaxLength(100);
    b.HasMany(u => u.Posts).WithOne();
});

We set up the relationship to be a one-to-many without explicit foreign key properties.

Adding a Migration

Now that Entity Framework is configured, we can move forward with creating the migration.

To learn more about Entity Framework migrations, check out our previous article on the topic.

When using the default MVC project with scaffolded identity and SQL Server, dotnet will automatically create the first migration for us. However, since we changed the primary key’s type, we should remove that migration and create our own first migration. To do this using the dotnet CLI, let’s issue the command from the project’s directory:

dotnet ef migrations remove

We can see, that the 00000000000000_CreateIdentitySchema migration has been removed, so now let’s add our new CreateIdentitySchema migration:

dotnet ef migrations add CreateIdentitySchema

By examining the generated migration, we can confirm that the custom properties and foreign keys are generated correctly:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "AspNetUsers",
        columns: table => new
        {
            Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
            DisplayName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
            LastLoginDateTime = table.Column<DateTime>(type: "datetime2", nullable: false),
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_AspNetUsers", x => x.Id);
        });

    migrationBuilder.CreateTable(
        name: "Post",
        columns: table => new
        {
            Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
            Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
            Text = table.Column<string>(type: "nvarchar(max)", nullable: false),
            ApplicationUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Post", x => x.Id);
            table.ForeignKey(
                name: "FK_Post_AspNetUsers_ApplicationUserId",
                column: x => x.ApplicationUserId,
                principalTable: "AspNetUsers",
                principalColumn: "Id");
        });
}

In this example, some parts of the migration are omitted to simplify the process of verifying the accurate generation of both primitive properties and the relationship with the related entity for the AspNetUsers table. Now we just have to issue the final command to apply the migration to the database:

dotnet ef database update

When checking the table structure, we will see that we have successfully extended the AspNetUsers table with our custom properties.

Conclusion

In this article, we’ve learned about extending IdentityUser with custom properties step by step. We created a descendant class and configured the identity framework to use this new class instead of IdentityUser. Then we added our custom properties and configured Entity Framework to store them correctly.

We also looked at changing the type of the primary key for our custom ApplicationUser, making the Guid persistence unique at the database level.

All in all, we can say that Microsoft did a great job in terms of extensibility with the ASP.NET Identity Framework, it is a fairly simple task to extend or completely change classes used by the Identity Framework.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!