72

I have an ASP.NET Core 2.1.0 application using EF Core 2.1.0.

How do I go about seeding the database with Admin user and give him/her an Admin role? I cannot find any documentation on this.

Askolein
  • 3,250
  • 3
  • 28
  • 40
J86
  • 14,345
  • 47
  • 130
  • 228
  • cloudscribe open source project provides a lot of missing pieces vs the standard project template including a ui for managing users, roles, etc https://marketplace.visualstudio.com/items?itemName=joeaudette.cloudscribeProjectTemplate and the source code on github has code that seeds initial content https://github.com/cloudscribe/cloudscribe – Joe Audette Jun 10 '18 at 15:06
  • There is documentation here https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding – Brad Jun 11 '18 at 00:42
  • 1
    I think that only works for seeding simple entities, not `ApplicationUser` where hashing of the passwords is required ...etc – J86 Jun 11 '18 at 09:49
  • Yes, good point. Another option is to inject UserManager into `Startup.Configure()` method and run a Task to create the admin user and role. – Brad Jun 12 '18 at 04:57
  • 2
    Excellent question, I wonder why the down votes. The Identity folks are leading us away from Roles (shouldn't use it) and into Claims, yet nothing out there on such a basic necessity without defeating the purpose of the changes moving from 2.0 to 2.1. I've been searching all day for the exact same thing... I bet you can do amazing stuff with this Core 2.1.0 if you could find relevant examples and updated documentation. It seems what they've created in 2.1 is so basic that you have no choice but to scaffold it out and start customizing away... but who knows... it's all just painful. – Sum None Jun 15 '18 at 17:10
  • @Ciwan did you find any solution for this ? – Zubair Rana Jul 21 '18 at 13:35
  • Yes, I ignored the way it is done in EF Core 2.1.0 and did it the way I was doing it in EF 2.0.0 – J86 Jul 22 '18 at 12:03
  • @Ciwan how ? Can you please share any helpful link ? – Zubair Rana Jul 27 '18 at 20:17
  • See my answer below @ZubairRana – J86 Aug 05 '18 at 06:50

7 Answers7

93

As user cannot be seeded in a normal way in Identity just like other tables are seeded using .HasData() of .NET Core 2.1.

Microsoft Recommendation: For data that requires calls to external API, such as ASP.NET Core Identity users creation it is recommended to use custom initialization logic.

Seed Roles in .NET Core 2.1 using code given below in ApplicationDbContext Class :

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "Admin", NormalizedName = "Admin".ToUpper() });
    }

Seed Users With Roles by Following the steps given below.

Step 1: New class creation

public static class ApplicationDbInitializer
{
    public static void SeedUsers(UserManager<IdentityUser> userManager)
    {
        if (userManager.FindByEmailAsync("abc@xyz.com").Result==null)
        {
            IdentityUser user = new IdentityUser
            {
                UserName = "abc@xyz.com",
                Email = "abc@xyz.com"
            };

            IdentityResult result = userManager.CreateAsync(user, "PasswordHere").Result;

            if (result.Succeeded)
            {
                userManager.AddToRoleAsync(user, "Admin").Wait();
            }
        }       
    }   
}

Step 2: Now Modify ConfigureServices method in Startup.cs class.

Before Modification:

services.AddDefaultIdentity<IdentityUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

After Modification:

services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

Step 3: Modify parameters of Configure Method in Startup.cs class.

Before Modification :

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        //..........
    }

After modification :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<IdentityUser> userManager)
    {
        //..........
    }

Step 4 : Calling method of our Seed (ApplicationDbInitializer) class:

ApplicationDbInitializer.SeedUsers(userManager);

Note: You can also Seed Roles just like users by Injecting the RoleManager along with UserManager.

Zubair Rana
  • 2,006
  • 2
  • 15
  • 38
  • 5
    One should be noted that ApplicationDbInitializer.SeedUsers() happens after the modelBuilder.Entity().HasData() if you use database migrations approach. – Stephen Zeng Nov 16 '18 at 08:35
  • 1
    Why you cannot seed a user just like other tables are seeded using `.HasData()` of .NET Core 2.1? – Hamza Khanzada Nov 22 '19 at 12:22
  • 1
    @HamzaKhanzada I have updated the answer. We can add user through `OnModelCreating()` method but it is not recommended. Microsoft recommendation came after 5 months of my answer that's why i was not aware of recommendation and came to know after your comment and thank you for highlighting it. – Zubair Rana Feb 07 '20 at 09:04
  • Do you have a source for that? Why is Identity an external API? – Jonathan Daniel Apr 14 '21 at 11:50
  • 1
    @JonathanDaniel https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding#model-seed-data – Zubair Rana Apr 15 '21 at 17:35
50

Actually a User Entity can be seeded in OnModelCreating, one thing to consider: the IDs should be predefined. If type string is used for TKey identity entities, then there is no problem at all.

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // any guid
    const string ADMIN_ID = "a18be9c0-aa65-4af8-bd17-00bd9344e575";
    // any guid, but nothing is against to use the same one
    const string ROLE_ID = ADMIN_ID;
    builder.Entity<IdentityRole>().HasData(new IdentityRole
    {
        Id = ROLE_ID,
        Name = "admin",
        NormalizedName = "admin"
    });

    var hasher = new PasswordHasher<UserEntity>();
    builder.Entity<UserEntity>().HasData(new UserEntity
    {
        Id = ADMIN_ID,
        UserName = "admin",
        NormalizedUserName = "admin",
        Email = "some-admin-email@nonce.fake",
        NormalizedEmail = "some-admin-email@nonce.fake",
        EmailConfirmed = true,
        PasswordHash = hasher.HashPassword(null, "SOME_ADMIN_PLAIN_PASSWORD"),
        SecurityStamp = string.Empty
    });

    builder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string>
    {
        RoleId = ROLE_ID,
        UserId = ADMIN_ID
    });
}
tenbits
  • 7,568
  • 5
  • 34
  • 53
  • 5
    Why should IDs be predefined? – Marie Oct 23 '18 at 17:05
  • 6
    @Marie, the `HasData` method ensures the entity exists by the `primary key`, so when the ID is not set, each time the seed method is executed, the new entity will be created. – tenbits Oct 24 '18 at 21:08
  • 1
    I kind of assumed that. You might want to add it to your answer – Marie Oct 24 '18 at 21:10
  • 5
    Works perfectly. PasswordHasher is part of NuGet package "Microsoft.Extensions.Identity.Core" if anyone needs to reference it. – thomasgalliker Apr 17 '19 at 08:51
  • 5
    Works perfectly except code will be generated in each new migration to update the password hash value (since it would be different each time it is run). – Ross Brasseaux Sep 06 '19 at 21:54
  • @Lopsided, why would the password hash be different each time its run even when I am using the same password? – Hamza Khanzada Nov 22 '19 at 12:16
  • @tenbits, thanks for the wonderful answer, but I don't know why Microsoft doesn't recommend seeding roles and users through `.HasData()`, They say `Data that requires calls to external API, such as ASP.NET Core Identity roles and users creation is recommended to use custom initialization logic` https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding#model-seed-data – Hamza Khanzada Nov 22 '19 at 12:27
  • @HamzaKhanzada Not sure. It has been the case for us though. The password hasher just generates a different hash. – Ross Brasseaux Nov 22 '19 at 14:17
  • what if the type `Guid` is used as `TKey` ? – A-Sharabiani Dec 28 '19 at 06:03
  • 1
    @A-Sharabiani works also. Change the IDs to smth. like this `new Guid("a18be9c0-aa65-4af8-bd17-00bd9344e575")` – tenbits Dec 28 '19 at 21:57
  • @HamzaKhanzada: This answer was working in my netcore2.2 project for a while now, but since i create a brand new netcore3.1 project, the password isnt accepted when i login. Perhaps this is the reason why MS doesnt recommend this approach? – jokab Feb 20 '20 at 13:40
  • 1
    @HamzaKhanzada It's because PasswordHasher generates salt every time and save it in the hash. https://andrewlock.net/exploring-the-asp-net-core-identity-passwordhasher/ – cathei May 07 '20 at 02:27
  • when i add , the Discriminator value is IdentityUser, and userManager.Users just return Discriminator ApplicationUser , How can i do , thank you – Z.W.Huang Jul 23 '21 at 04:25
  • Usin this way it saves the data correctly but for some reason when I try to login userManager can't find by email. the only way to get the user is finding by Id. – Careuno Merchan Aug 15 '21 at 23:50
  • Why is `SecurityStamp` set to `string.Empty`, instead of a value? – JAmes Dec 07 '21 at 01:39
  • IF I do it this way but was doing the seed way would this delete all my existing users out of the tabels? – c-sharp-and-swiftui-devni Sep 05 '22 at 20:31
29

ASP.Net Core 3.1

That's how I do it using the EntityTypeBuilder :

Role Configuration:

public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole>
{
    private const string adminId = "2301D884-221A-4E7D-B509-0113DCC043E1";
    private const string employeeId = "7D9B7113-A8F8-4035-99A7-A20DD400F6A3";
    private const string sellerId = "78A7570F-3CE5-48BA-9461-80283ED1D94D";
    private const string customerId = "01B168FE-810B-432D-9010-233BA0B380E9";

    public void Configure(EntityTypeBuilder<IdentityRole> builder)
    {

        builder.HasData(
                new IdentityRole
                {
                    Id = adminId,
                    Name = "Administrator",
                    NormalizedName = "ADMINISTRATOR"
                },
                new IdentityRole
                {
                    Id = employeeId,
                    Name = "Employee",
                    NormalizedName = "EMPLOYEE"
                },
                new IdentityRole
                {
                    Id = sellerId,
                    Name = "Seller",
                    NormalizedName = "SELLER"
                },
                new IdentityRole
                {
                    Id = customerId,
                    Name = "Customer",
                    NormalizedName = "CUSTOMER"
                }
            );
    }
}

User Configuration:

public class AdminConfiguration : IEntityTypeConfiguration<ApplicationUser>
{
    private const string adminId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";

    public void Configure(EntityTypeBuilder<ApplicationUser> builder)
    {
        var admin = new ApplicationUser
        {
            Id = adminId,
            UserName = "masteradmin",
            NormalizedUserName = "MASTERADMIN",
            FirstName = "Master",
            LastName = "Admin",
            Email = "Admin@Admin.com",
            NormalizedEmail = "ADMIN@ADMIN.COM",
            PhoneNumber = "XXXXXXXXXXXXX",
            EmailConfirmed = true,
            PhoneNumberConfirmed = true,
            BirthDate = new DateTime(1980,1,1),
            SecurityStamp = new Guid().ToString("D"),
            UserType = UserType.Administrator                
        };

        admin.PasswordHash = PassGenerate(admin);

        builder.HasData(admin);
    }

    public string PassGenerate(ApplicationUser user)
    {
        var passHash = new PasswordHasher<ApplicationUser>();
        return passHash.HashPassword(user, "password");
    }
}

Assigning Roles To Users:

 public class UsersWithRolesConfig : IEntityTypeConfiguration<IdentityUserRole<string>>
    {
        private const string adminUserId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";
        private const string adminRoleId = "2301D884-221A-4E7D-B509-0113DCC043E1";

        public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder)
        {
            IdentityUserRole<string> iur = new IdentityUserRole<string>
            {
                RoleId = adminRoleId,
                UserId = adminUserId
            };

            builder.HasData(iur);
        }
    }

Finally in the DB Context class:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    //If you have alot of data configurations you can use this (works from ASP.Net core 2.2):

    //This will pick up all configurations that are defined in the assembly
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());

    //Instead of this:
    modelBuilder.ApplyConfiguration(new RoleConfiguration());
    modelBuilder.ApplyConfiguration(new AdminConfiguration());
    modelBuilder.ApplyConfiguration(new UsersWithRolesConfig());
}
Minimal
  • 56
  • 6
HMZ
  • 2,949
  • 1
  • 19
  • 30
  • Nice and clean way of populating the users and other data much better than other methods – Andy Braham Jun 28 '20 at 19:22
  • 1
    I was thinking that it was executing on each startup but I also had had to run `dotnet ef migrations add SeedRoles` and `dotnet ef database update` to create and apply migrations for the inserts – Yehor Androsov Jul 31 '20 at 14:43
  • 2
    @YegorAndrosov If you want to avoid the migration part move your logic out of the `OnModelCreating` and use the `UserManager` and `RoleManager` instead of inserting raw values. I still think its cleaner this way and better for seeding other types of data. – HMZ Jul 31 '20 at 16:24
  • 1
    ok, it was just confusing to have breakpoints hit on startup, but seeing empty tables in db. did not see any mentions that this applies to migrations. so i thought it might help for someone else – Yehor Androsov Aug 01 '20 at 11:31
  • 1
    Nice. Just had to replace `new Guid().ToString("D")` with `Guid.NewGuid().ToString()`. – Milan Jul 24 '21 at 16:31
7

Here is how I did it in the end. I created a DbInitializer.cs class to do the seeding of all my data (including the admin user).

screenshot

Here's the code for the methods relating to the seeding of the user accounts:

private static async Task CreateRole(RoleManager<IdentityRole> roleManager, 
ILogger<DbInitializer> logger, string role)
{
  logger.LogInformation($"Create the role `{role}` for application");
  IdentityResult result = await roleManager.CreateAsync(new IdentityRole(role));
  if (result.Succeeded)
  {
    logger.LogDebug($"Created the role `{role}` successfully");
  }
  else
  {
    ApplicationException exception = new ApplicationException($"Default role `{role}` cannot be created");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(result));
    throw exception;
  }
}

private static async Task<ApplicationUser> CreateDefaultUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string displayName, string email)
{
  logger.LogInformation($"Create default user with email `{email}` for application");

  ApplicationUser user = new ApplicationUser
  {
    DisplayUsername = displayName,
    Email = email,
    UserName = email
  };

  IdentityResult identityResult = await userManager.CreateAsync(user);

  if (identityResult.Succeeded)
  {
    logger.LogDebug($"Created default user `{email}` successfully");
  }
  else
  {
    ApplicationException exception = new ApplicationException($"Default user `{email}` cannot be created");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
    throw exception;
  }

  ApplicationUser createdUser = await userManager.FindByEmailAsync(email);
  return createdUser;
}

private static async Task SetPasswordForUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string email, ApplicationUser user, string password)
{
  logger.LogInformation($"Set password for default user `{email}`");
  IdentityResult identityResult = await userManager.AddPasswordAsync(user, password);
  if (identityResult.Succeeded)
  {
    logger.LogTrace($"Set password `{password}` for default user `{email}` successfully");
  }
  else
  {
    ApplicationException exception = new ApplicationException($"Password for the user `{email}` cannot be set");
    logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
    throw exception;
  }
}

My Program.cs looks like this:

public class Program
{
  public static async Task Main(string[] args)
  {
    var host = BuildWebHost(args);

    using (var scope = host.Services.CreateScope())
    {
      var services = scope.ServiceProvider;
      Console.WriteLine(services.GetService<IConfiguration>().GetConnectionString("DefaultConnection"));
      try
      {
        var context = services.GetRequiredService<PdContext>();
        var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
        var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();

        var dbInitializerLogger = services.GetRequiredService<ILogger<DbInitializer>>();
        await DbInitializer.Initialize(context, userManager, roleManager, dbInitializerLogger);
      }
      catch (Exception ex)
      {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
      }
    }

    host.Run();
  }

  public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .Build();
}
MWD
  • 1,632
  • 2
  • 18
  • 39
J86
  • 14,345
  • 47
  • 130
  • 228
1

This is based on .NET 6 with Individual user accounts and then scaffolding Identity. The user is created and then gets a confirmed email based on Microsofts code.

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-6.0&tabs=visual-studio#scaffold-identity-into-a-razor-project-with-authorization

You can then seed the role per @Zubair Rana answer.

https://stackoverflow.com/a/51571555/3850405

Program.cs:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        CreateDbAndRunMigrations(host);
        host.Run();
    }

    private static void CreateDbAndRunMigrations(IHost host)
    {
        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            var context = services.GetRequiredService<ApplicationDbContext>();
            context.Database.Migrate();

            var userStore = services.GetRequiredService<IUserStore<ApplicationUser>>();

            var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();

            DbInitializer.Initialize(context, userManager, userStore);
        }
    }
}

DbInitializer.cs:

public static class DbInitializer
{
    public static void Initialize(ApplicationDbContext context, UserManager<ApplicationUser> userManager, IUserStore<ApplicationUser> userStore)
    {
        if (context.Users.Any())
        {
            return;   // DB has been seeded
        }

        var user = Activator.CreateInstance<ApplicationUser>();

        var email = "example@example.com";

        var emailStore = (IUserEmailStore<ApplicationUser>)userStore;

        //Will not be used - Has to use Forgot Password. Last characters used to make sure password validation passes
        var password = GetUniqueKey(40) + "aA1!";

        userStore.SetUserNameAsync(user, email, CancellationToken.None).Wait();
        emailStore.SetEmailAsync(user, email, CancellationToken.None).Wait();
        var result = userManager.CreateAsync(user, password).Result;

        if (result.Succeeded)
        {
            var userId = userManager.GetUserIdAsync(user).Result;
            var code =  userManager.GenerateEmailConfirmationTokenAsync(user).Result;
            userManager.ConfirmEmailAsync(user, code).Wait();
        }
        else
        {
            throw new Exception();
        }
    }
    
    private static string GetUniqueKey(int size)
    {
        var chars =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!+?*~".ToCharArray();

        byte[] data = new byte[4*size];
        using (var crypto = RandomNumberGenerator.Create())
        {
            crypto.GetBytes(data);
        }
        StringBuilder result = new StringBuilder(size);
        for (int i = 0; i < size; i++)
        {
            var rnd = BitConverter.ToUInt32(data, i * 4);
            var idx = rnd % chars.Length;

            result.Append(chars[idx]);
        }

        return result.ToString();
    }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

If you are referring to Identity users, the way we did was to add hardcoded values in DbContext.OnModelCreating:

builder.Entity<Role>().HasData(new Role { Id = 2147483645, Name = UserRole.Admin.ToString(), NormalizedName = UserRole.Admin.ToString().ToUpper(), ConcurrencyStamp = "123c90a4-dfcb-4e77-91e9-d390b5b6e21b" });

And user:

builder.Entity<User>().HasData(new User
        {
            Id = 2147483646,
            AccessFailedCount = 0,
            PasswordHash = "SomePasswordHashKnownToYou",
            LockoutEnabled = true,
            FirstName = "AdminFName",
            LastName = "AdminLName",
            UserName = "admin",
            Email = "admin@gmail.com",
            EmailConfirmed = true,
            InitialPaymentCompleted = true,
            MaxUnbalancedTech = 1,
            UniqueStamp = "2a1a39ef-ccc0-459d-aa9a-eec077bfdd22",
            NormalizedEmail = "ADMIN@GMAIL.COM",
            NormalizedUserName = "ADMIN",
            TermsOfServiceAccepted = true,
            TermsOfServiceAcceptedTimestamp = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
            SecurityStamp = "ce907fd5-ccb4-4e96-a7ea-45712a14f5ef",
            ConcurrencyStamp = "32fe9448-0c6c-43b2-b605-802c19c333a6",
            CreatedTime = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
            LastModified = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc)
        });

builder.Entity<UserRoles>().HasData(new UserRoles() { RoleId = 2147483645, UserId = 2147483646 });

I wish there was some better/cleaner way to do it.

Salih H.
  • 102
  • 7
0

Here's how I created an admin role and ensured it was added to my admin user in dotnet 6 with very few lines of code using EF core

In your db context class:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<IdentityRole>().HasData(
        new IdentityRole { Name = "Admin", NormalizedName = "Admin".ToUpper() }
    );
}

In your Program.cs file:

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var userManager = services.GetRequiredService<UserManager<User>>();
    var admin = await userManager.FindByEmailAsync("admin@admin.com");
    if (admin != null)
    {
        if (!await userManager.IsInRoleAsync(admin, "Admin"))
            await userManager.AddToRoleAsync(admin, "Admin");
    }
}

app.Run();
Steji
  • 580
  • 1
  • 6
  • 17