In ASP.NET Core-6 Web API, I am implementing User Authentication using Identity DB Context and Entity Framework.
I have this code.
Model:
public class AppUser : IdentityUser
{
public bool IsActive { get; set; }
public Guid RefreshToken { get; set; }
public DateTime RefreshTokenExpiryTime { get; set; }
}
Dto:
public class RegisterUserDto
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
public class LoginResponseDto
{
public string Id { get; set; }
public string Token { get; set; }
public Guid RefreshToken { get; set; }
}
public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
Then I have this Interface Service for Authentication.
IAuthenticationService:
public interface IAuthenticationService
{
Task<Response<string>> Register(RegisterUserDto userDto);
Task<Response<LoginResponseDto>> Login(LoginDto loginDto);
}
Then I have the Service implementation for User Registration and User Login.
AuthenticationService:
public class AuthenticationService : IAuthenticationService
{
private readonly UserManager<AppUser> _userManager;
private readonly IMapper _mapper;
private readonly ITokenGeneratorService _tokenGenerator;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
public AuthenticationService(UserManager<AppUser> userManager, IUnitOfWork unitOfWork, ILogger logger,
IMapper mapper, ITokenGeneratorService tokenGenerator)
{
_userManager = userManager;
_mapper = mapper;
_tokenGenerator = tokenGenerator;
_unitOfWork = unitOfWork;
_logger = logger;
}
private async Task<Response<bool>> ValidateUser(LoginDto model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var response = new Response<bool>();
if(user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
{
response.Message = "Invalid Credentials";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
if(!await _userManager.IsEmailConfirmedAsync(user) && user.IsActive)
{
response.Message = "Account not activated";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.Forbidden;
return response;
}
else
{
response.Succeeded = true;
return response;
}
}
public async Task<Response<LoginResponseDto>> Login(LoginDto model)
{
var response = new Response<LoginResponseDto>();
var validityResult = await ValidateUser(model);
if (!validityResult.Succeeded)
{
_logger.Error("Login operation failed");
response.Message = validityResult.Message;
response.StatusCode = validityResult.StatusCode;
response.Succeeded = false;
return response;
}
var user = await _userManager.FindByEmailAsync(model.Email);
var refreshToken = _tokenGenerator.GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
var result = new LoginResponseDto()
{
Id = user.Id,
Token = await _tokenGenerator.GenerateToken(user),
RefreshToken = refreshToken
};
await _userManager.UpdateAsync(user);
_logger.Information("User successfully logged in");
response.StatusCode = (int)HttpStatusCode.OK;
response.Message = "Login Successfully";
response.Data = result;
response.Succeeded = true;
return response;
}
public async Task<Response<string>> Register(RegisterUserDto model)
{
var user = _mapper.Map<AppUser>(model);
user.IsActive = true;
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, UserRoles.Customer);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = TokenConverter.EncodeToken(token);
var userRole = await _userManager.GetRolesAsync(user);
var customer = new Customer
{
AppUser = user
};
await _unitOfWork.Customers.InsertAsync(customer);
await _unitOfWork.Save();
response.StatusCode = (int)HttpStatusCode.Created;
response.Succeeded = true;
response.Data = user.Id;
response.Message = "User created successfully!";
transaction.Complete();
return response;
}
response.Message = GetErrors(result);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Succeeded = false;
transaction.Complete();
return response;
};
}
}
And finally the Controller.
Controller
public class AuthenticationController : ControllerBase
{
private readonly ILogger _logger;
private readonly IAuthenticationService _authService;
public AuthenticationController(ILogger logger, IAuthenticationService authService)
{
_logger = logger;
_authService = authService;
}
[HttpPost]
[Route("register")]
public async Task<ActionResult<Response<LoginResponseDto>>> Register([FromBody] RegisterUserDto model)
{
_logger.Information($"Registration Attempt for {model.Email}");
var result = await _authService.Register(model);
return StatusCode(result.StatusCode, result);
}
[HttpPost]
[Route("login")]
public async Task<ActionResult<Response<string>>> Login([FromBody] LoginDto model)
{
_logger.Information($"Login Attempt for {model.Email}");
var result = await _authService.Login(model);
return StatusCode(result.StatusCode, result);
}
}
At the moment, I use the Register Method to register User and the Login Method for user Login and Authentication.
In the Login Service method, the user credentials are validated. Then if successful, he logs in.
However, I want to change this based on demand. If a user tries to login and is validated. If he does not exist, the application should use the provided credentials to Register the user, validate him, and also login the user automatically (note that Email should also be used as UserName).
How do I achieve this using the Login Service method?
If i understood correctly you should code to your ValidateUser that checks if user exists or not. If the user dosen't exist it should register it , validate it's email and re-log it properly.
public class AuthenticationService : IAuthenticationService
{
private readonly UserManager<AppUser> _userManager;
private readonly IMapper _mapper;
private readonly ITokenGeneratorService _tokenGenerator;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
public AuthenticationService(UserManager<AppUser> userManager, IUnitOfWork unitOfWork, ILogger logger,
IMapper mapper, ITokenGeneratorService tokenGenerator)
{
_userManager = userManager;
_mapper = mapper;
_tokenGenerator = tokenGenerator;
_unitOfWork = unitOfWork;
_logger = logger;
}
private async Task<Response<bool>> ValidateUser(LoginDto model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var response = new Response<bool>();
if (user == null)
{
var registerModel = new RegisterUserDto
{
Email = model.Email,
UserName = model.Email,
Password = model.Password,
};
var resgistration = await Register(registerModel);
if(resgistration.Succeeded)
{
var token = _userManager.GenerateUserTokenAsync(user);
_userManager.ConfirmEmailAsync(user, token);
}
return Login(model);
}
if (!await _userManager.CheckPasswordAsync(user, model.Password))
{
response.Message = "Invalid Credentials";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
if (!await _userManager.IsEmailConfirmedAsync(user) && user.IsActive)
{
response.Message = "Account not activated";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.Forbidden;
return response;
}
else
{
response.Succeeded = true;
return response;
}
}
public async Task<Response<LoginResponseDto>> Login(LoginDto model)
{
var response = new Response<LoginResponseDto>();
var validityResult = await ValidateUser(model);
if (!validityResult.Succeeded)
{
_logger.Error("Login operation failed");
response.Message = validityResult.Message;
response.StatusCode = validityResult.StatusCode;
response.Succeeded = false;
return response;
}
var user = await _userManager.FindByEmailAsync(model.Email);
var refreshToken = _tokenGenerator.GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
var result = new LoginResponseDto()
{
Id = user.Id,
Token = await _tokenGenerator.GenerateToken(user),
RefreshToken = refreshToken
};
await _userManager.UpdateAsync(user);
_logger.Information("User successfully logged in");
response.StatusCode = (int)HttpStatusCode.OK;
response.Message = "Login Successfully";
response.Data = result;
response.Succeeded = true;
return response;
}
public async Task<Response<string>> Register(RegisterUserDto model)
{
var user = _mapper.Map<AppUser>(model);
user.IsActive = true;
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, UserRoles.Customer);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = TokenConverter.EncodeToken(token);
var userRole = await _userManager.GetRolesAsync(user);
var customer = new Customer
{
AppUser = user
};
await _unitOfWork.Customers.InsertAsync(customer);
await _unitOfWork.Save();
response.StatusCode = (int)HttpStatusCode.Created;
response.Succeeded = true;
response.Data = user.Id;
response.Message = "User created successfully!";
transaction.Complete();
return response;
}
response.Message = GetErrors(result);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Succeeded = false;
transaction.Complete();
return response;
};
}
}
I am implementing User Authentication using Identity DB Context and Entity Framework in In ASP.NET Core-6 Web API
Here is my code:
Model:
public class AppUser : IdentityUser
{
public bool IsActive { get; set; }
public Guid RefreshToken { get; set; }
public DateTime RefreshTokenExpiryTime { get; set; }
}
Dto:
public class RegisterUserDto
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
public class LoginResponseDto
{
public string Id { get; set; }
public string Token { get; set; }
public Guid RefreshToken { get; set; }
}
public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
AuthenticationService:
public class AuthenticationService : IAuthenticationService
{
private readonly UserManager<AppUser> _userManager;
private readonly IMapper _mapper;
private readonly ITokenGeneratorService _tokenGenerator;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
public AuthenticationService(UserManager<AppUser> userManager, IUnitOfWork unitOfWork, ILogger logger,
IMapper mapper, ITokenGeneratorService tokenGenerator)
{
_userManager = userManager;
_mapper = mapper;
_tokenGenerator = tokenGenerator;
_unitOfWork = unitOfWork;
_logger = logger;
}
private async Task<Response<bool>> ValidateUser(LoginDto model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var response = new Response<bool>();
if (user == null)
{
var registerModel = new RegisterUserDto
{
Email = model.Email,
UserName = model.Email,
Password = model.Password,
};
var resgistration = await Register(registerModel);
if(resgistration.Succeeded)
{
var token = _userManager.GenerateUserTokenAsync(user);
_userManager.ConfirmEmailAsync(user, token);
}
return Login(model);
}
if (!await _userManager.CheckPasswordAsync(user, model.Password))
{
response.Message = "Invalid Credentials";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
if (!await _userManager.IsEmailConfirmedAsync(user) && user.IsActive)
{
response.Message = "Account not activated";
response.Succeeded = false;
response.StatusCode = (int)HttpStatusCode.Forbidden;
return response;
}
else
{
response.Succeeded = true;
return response;
}
}
public async Task<Response<LoginResponseDto>> Login(LoginDto model)
{
var response = new Response<LoginResponseDto>();
var validityResult = await ValidateUser(model);
if (!validityResult.Succeeded)
{
_logger.Error("Login operation failed");
response.Message = validityResult.Message;
response.StatusCode = validityResult.StatusCode;
response.Succeeded = false;
return response;
}
var user = await _userManager.FindByEmailAsync(model.Email);
var refreshToken = _tokenGenerator.GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
var result = new LoginResponseDto()
{
Id = user.Id,
Token = await _tokenGenerator.GenerateToken(user),
RefreshToken = refreshToken
};
await _userManager.UpdateAsync(user);
_logger.Information("User successfully logged in");
response.StatusCode = (int)HttpStatusCode.OK;
response.Message = "Login Successfully";
response.Data = result;
response.Succeeded = true;
return response;
}
public async Task<Response<string>> Register(RegisterUserDto model)
{
var user = _mapper.Map<AppUser>(model);
user.IsActive = true;
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, UserRoles.Customer);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = TokenConverter.EncodeToken(token);
var userRole = await _userManager.GetRolesAsync(user);
var customer = new Customer
{
AppUser = user
};
await _unitOfWork.Customers.InsertAsync(customer);
await _unitOfWork.Save();
response.StatusCode = (int)HttpStatusCode.Created;
response.Succeeded = true;
response.Data = user.Id;
response.Message = "User created successfully!";
transaction.Complete();
return response;
}
response.Message = GetErrors(result);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Succeeded = false;
transaction.Complete();
return response;
};
}
}
This is what I want to achieve:
At login, I want to use ValidateUser to checks if user exists or not. If the user doesn't exist it should register it, validate it's email and re-login to it properly.
I got this error:
Cannot implicitly convert type 'Response<LoginResponseDto>' to 'Response<bool>'
Then, return Login(model) is highlighted in:
private async Task<Response<bool>> ValidateUser(LoginDto model)
How do I resolve this?
I am getting the oddest of errors when I attempt to assign roles to user the role is their in the db fine.
But the function used to assign roles is causing an exception
public static async Task<IdentityResult>
AssignRoles(IServiceProvider services,ApplicationUser user,
string email, string[] roles)
{
IdentityResult result = new IdentityResult();
UserManager<ApplicationUser> _userManager =
services.GetService<UserManager<ApplicationUser>>();
result = await _userManager.AddToRolesAsync(user, roles);
return result;
}
The exception that I am getting is after the AddToRolesAsync is called
Invalid Operation the connection is closed
Code I use to call above
public async static void CreateParent(IServiceProvider serviceProvider)
{
var context = new DBContext();//
serviceProvider.GetService<DBContext>();
string[] roles = new string[] { "Parent" };
foreach (string role in roles)
{
var roleStore = new RoleStore<IdentityRole>(context);
if (!context.Roles.Any(r => r.Name == role))
{
await roleStore.CreateAsync(new IdentityRole(role));
}
}
var user = new ApplicationUser
{
FirstName = "Parent",
LastName = "Two",
Email = "parenttwo#apps-mobileapp.com",
NormalizedEmail ="PARENTTWO#APPS-MOBILEAPP.COM",
UserName = "parenttwo#apps-MOBILEAPP.com",
NormalizedUserName = "PARENTTWO#APPS-MOBILEAPP.COM",
PhoneNumber = "+111111111111",
EmailConfirmed = true,
PhoneNumberConfirmed = true,
SecurityStamp = Guid.NewGuid().ToString("D")
};
var db = new DBContext();
if (!db.Users.Any(u => u.UserName == user.UserName))
{
var password = new PasswordHasher<ApplicationUser>();
var hashed = password.HashPassword(user, "Test12345!");
user.PasswordHash = hashed;
var userStore = new UserStore<ApplicationUser>(context);
var result = await userStore.CreateAsync(user);
}
await AssignRoles(serviceProvider,user, user.Email, roles);
await db.SaveChangesAsync();
}
But its the AddToRolesAsync it fails with the error mentioned above, I am calling it form the startup class as such?
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,IServiceProvider
service)
{
SampleData.CreateParent(service);
}
Till .net5 I've been Seeding data using the following in startup.cs file:
SeedData.Seed(_userManager, _roleManager);
And then in a seperate file SeedData.cs, the following code:
public static class SeedData
{
public static void Seed(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
SeedRoles(roleManager);
SeedUsers(userManager);
}
private static void SeedUsers(UserManager<IdentityUser> userManager)
{
if(userManager.FindByNameAsync("admin#localhost.com").Result == null)
{
var user = new IdentityUser
{
UserName = "admin#localhost.com",
Email = "admin#localhost.com"
};
var result = userManager.CreateAsync(user, "P#ssword1").Result;
if(result.Succeeded)
{
userManager.AddToRoleAsync(user, "Administrator").Wait();
}
}
}
private static void SeedRoles(RoleManager<IdentityRole> roleManager)
{
if(!roleManager.RoleExistsAsync("Administrator").Result)
{
var role = new IdentityRole
{
Name = "Administrator",
};
var result = roleManager.CreateAsync(role).Result;
}
if(!roleManager.RoleExistsAsync("Employee").Result)
{
var role = new IdentityRole
{
Name = "Employee",
};
var result = roleManager.CreateAsync(role).Result;
}
}
}
Now, how do i do the same with .net6, since it has only program.cs file?
This is what I personally do:
I make an extension to IApplicationBuilder:
public static class ApplicationBuilderExtensions
{
public static async Task<IApplicationBuilder> PrepareDatabase(this IApplicationBuilder app)
{
using var scopedServices = app.ApplicationServices.CreateScope();
var serviceProvider = scopedServices.ServiceProvider;
var data = serviceProvider.GetRequiredService<NeonatologyDbContext>();
data.Database.Migrate();
await SeedAdministratorAsync(serviceProvider);
await SeedDoctorAsync(data, serviceProvider);
return app;
}
Here are the seedings:
private static async Task SeedDoctorAsync(NeonatologyDbContext data, IServiceProvider serviceProvider)
{
if (data.Doctors.Any())
{
return;
}
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = serviceProvider.GetRequiredService<RoleManager<ApplicationRole>>();
var identityRole = new ApplicationRole()
{
Name = DoctorRoleName
};
await roleManager.CreateAsync(identityRole);
var city = await data.Cities.Where(x => x.Name == "Плевен").FirstOrDefaultAsync();
var doctor = new ApplicationUser()
{
Email = DoctorEmail,
UserName = DoctorEmail,
EmailConfirmed = true,
Doctor = new Doctor
{
FirstName = DoctorFirstName,
LastName = DoctorLastName,
PhoneNumber = DoctorPhone,
Address = Address,
Age = DoctorAge,
Biography = Biography,
CityId = city.Id,
City = city,
YearsOfExperience = YearsOfExperience,
Email = DoctorEmail
}
};
await userManager.CreateAsync(doctor, DoctorPassword);
await userManager.AddToRoleAsync(doctor, identityRole.Name);
doctor.Doctor.UserId = doctor.Id;
doctor.Doctor.Image = new Image()
{
RemoteImageUrl = "SomeURL"
};
await data.SaveChangesAsync();
}
private static async Task SeedAdministratorAsync(IServiceProvider serviceProvider)
{
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = serviceProvider.GetRequiredService<RoleManager<ApplicationRole>>();
if (await roleManager.RoleExistsAsync(AdministratorRoleName))
{
return;
}
var identityRole = new ApplicationRole()
{
Name = AdministratorRoleName
};
await roleManager.CreateAsync(identityRole);
const string adminEmail = AdministratorEmail;
const string adminPassword = AdministratorPassword;
var adminUser = new ApplicationUser()
{
Email = adminEmail,
UserName = adminEmail,
EmailConfirmed = true
};
if (await userManager.IsInRoleAsync(adminUser, identityRole.Name))
{
return;
}
await userManager.CreateAsync(adminUser, adminPassword);
await userManager.AddToRoleAsync(adminUser, identityRole.Name);
}
And in the Program.cs I have:
app.PrepareDatabase()
.GetAwaiter()
.GetResult();
The following snippet works for me and seeds the data upon initialization of the application.
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.
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.
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
});
}
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());
}
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).
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();
}
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();
}
}
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.
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();