I'm creating an application in C# that uses Identity.
I created my own ApplicationUser:
public class ApplicationUser: IdentityUser
{
public string Naam { get; set; }
public string Zipcode { get; set; }
public bool Active { get; set; }
public UserRole HighestRole { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
I also created a Usermanager:
public class UserManager : UserManager<Gebruiker>
{
public UserManager(IUserStore<Gebruiker> store)
: base(store)
{
}
public static UserManager Create(IdentityFactoryOptions<UserManager> options,IOwinContext context)
{
var manager = new UserManager(new UserStore<Gebruiker>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<Gebruiker>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<Gebruiker>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
But if I want to get a user out of my database, the UserRole object is always null
this is how I get the user out of the database.
ApplicationUser user = UserManager.FindById(correctId);
CODE:
SAME TIME IN DATABASE:
public virtual UserRole HighestRole { get; set; }
Related
In ASP.NET Core-6 Web API, I have this code:
Models:
public class AuthResult
{
public string AccessToken { get; set; }
public string TokenType { get; set; }
public int ExpiresIn { get; set; }
public string RefreshToken { get; set; }
}
public class JwtTokenManager : IJwtTokenManager
{
private readonly JwtSettings _jwtSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly TokenValidationParameters _tokenValidationParameters;
public async Task<AuthResult> GenerateClaimsTokenAsync(string username, CancellationToken cancellationToken)
{
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // TODO: encrypt user id for added security
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddMinutes(5)).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}),
Expires = DateTime.UtcNow.Add(_jwtSettings.Expiration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var refreshTokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // TODO: encrypt user id for added security
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Iss, _jwtSettings.Issuer),
new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
}),
Expires = DateTime.UtcNow.Add(_jwtSettings.Expiration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
// Create JWT tokens
var token = tokenHandler.CreateToken(tokenDescriptor);
var refreshtoken = tokenHandler.CreateToken(refreshTokenDescriptor);
return new AuthResult
{
AccessToken = tokenHandler.WriteToken(token),
TokenType = "Bearer",
ExpiresIn = _jwtSettings.Expiration.Seconds,
RefreshToken = tokenHandler.WriteToken(refreshtoken)
};
}
}
Identity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobileNumber { get; set; }
}
UserDto:
public class UserDto
{
public long Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
}
Then, finally I have SignInCommandHandler
public class SignInCommandHandler : IRequestHandler<SignInCommand, AuthResult>
{
private readonly ISignInManager _signInManager;
private readonly IJwtTokenManager _jwtTokenManager;
public SignInCommandHandler(ISignInManager signInManager, IJwtTokenManager jwtTokenManager)
{
_signInManager = signInManager;
_jwtTokenManager = jwtTokenManager;
}
public async Task<AuthResult> Handle(SignInCommand request, CancellationToken cancellationToken)
{
// validate username & password
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, false);
// Throw exception if credential validation failed
if (!result.Successful)
{
throw new UnauthorizedException("Invalid username or password.");
}
// Generate JWT token response if validation successful
AuthResult response = await _jwtTokenManager.GenerateClaimsTokenAsync(request.Username, cancellationToken);
return response;
}
}
I have done something like:
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
in JwtTokenManager
How do I involve the detail of the logged in user and his roles in the
return response?
Thank you
(Environment: Visual Studio 2019 v16.4.3)
I create a new "ASP.NET Core Web Application" with the following options
ASP.Net Core 3.1
Angular
Authentication of Individual User Account (with "Store user accounts in-app", the only option)
Then I did the following to add all the scaffolded items.
From Solution Explorer, right-click on the project > Add > New Scaffolded Item
Added new fields FirstName and LastName in the class ApplicationUser and updated the code to enable the registration with the new fields. The first name and last name can be saved to the database after registration.
Now I need to modify the code to enable the user to modify the customized fields FirstName and LastName. The following shows the class (I made some changes for FirstName and LastName). In the method OnPostAsync(), it calls _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber) to change the values. How to define _userManager.SetFistNameAsync(user, Input.PhoneNumber) and _userManager.SetLastNameAsync(user, Input.PhoneNumber)?
\Areas\Identity\Pages\Account\Manage\Index.cshtml.cs
public partial class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public IndexModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public string Username { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
[Display(Name = "First name")]
public string FirstName { get; set; }
[Display(Name = "Last name")]
public string LastName { get; set; }
}
private async Task LoadAsync(ApplicationUser user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
PhoneNumber = phoneNumber,
FirstName = user.FirstName,
LastName = user.LastName
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
}
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
It's been awhile since I've worked with the ASP.NET Core Identity stuff, but I believe you update your custom user object from here (GetUserAsync should return your custom ApplicationUser class):
var user = await _userManager.GetUserAsync(User);
user.FirstName = Input.FirstName;
user.LastName = Input.LastName;
Then call _userManager.UpdateAsync(user):
await _userManager.UpdateAsync(user);
I'm getting this error even though I only have one GET on the page. So I'm thoroughly confused.
Here's what the whole login page looks like:
public class LoginModel : PageModel
{
private UserAuthenticationService _userAuthenticationService { get; set; }
private ClaimService _claimService { get; set; }
public LoginModel(UserAuthenticationService userAuthenticationService, ClaimService claimService)
{
_userAuthenticationService = userAuthenticationService;
_claimService = claimService;
}
public async Task OnGetAsync()
{
var user = _userAuthenticationService.Login();
if (user == null)
{
//TODO: Login failed
}
else
{
var claims = _claimService.GetClaimsFromUserModel(user);
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties()
{
ExpiresUtc = DateTime.UtcNow.AddDays(6),
IsPersistent = true,
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), authProperties);
}
}
And here's the error. I only have one OnGet, so I don't know why it's saying there are multiple. Any ideas?
I am trying to supply an ApplicationUser to my mocked ApplicationUserManager. When I try and add roles I notice that Roles is a read-only property. Not sure the best way to implement in my test.
[TestMethod]
public void AccountPerission_PermissionScope()
{
//Arrange
IRolePermissionSvc roleService = GetRoleService();
IUserPermissionSvc userService = GetUserService();
AccountController controller = new AccountController(
_applicationUserManager.Object,
null,
_staffTypeSvc.Object,
_militaryBaseSvc.Object,
_positionTitleSvc.Object,
userService,
roleService
);
var appUser = new ApplicationUser()
{
FirstName = "test",
LastName = "tester",
Id = "02dfeb89-9b80-4884-b694-862adf38f09d",
Roles = new List<ApplicationRoles> { new ApplicationRole { Id = "1" } } // This doesn't work.
};
//Act
_applicationUserManager.Setup(x => x.FindByIdAsync(It.IsAny<string>())).Returns(Task.FromResult<ApplicationUser>(appUser));
Task<PartialViewResult> result = controller.AjaxResetUserPermission(appUser.Id, 1);
//Assert
Assert.IsNotNull(result);
}
While Roles is a read-only property, it is virtual which means that it can be overridden in a derived class. So you can either create a class derived from ApplicationUser
public class MockApplicationUser : ApplicationUser {
private readonly ICollection<ApplicationRoles> roles
public MockedApplicationUser(List<ApplicationRoles> roles) : base() {
this.roles = roles;
}
public override ICollection<ApplicationRoles> Roles {
get { return roles; }
}
}
and use that in the test
var appUser = new MockApplicationUser(new List<ApplicationRoles> { new ApplicationRole { Id = "1" } })
{
FirstName = "test",
LastName = "tester",
Id = "02dfeb89-9b80-4884-b694-862adf38f09d"
};
or mock the ApplicationUser and setup the Roles property
var roles = new List<ApplicationRoles> { new ApplicationRole { Id = "1" } };
var appUserMock = new Mock<ApplicationUser>();
appUserMock.SetupAllProperties();
appUserMock.Setup(m => m.Roles).Returns(roles);
var appUser = appUserMock.Object;
appUser.FirstName = "test";
appUser.LastName = "tester";
appUser.Id = "02dfeb89-9b80-4884-b694-862adf38f09d";
As an aside, the test method can also be made async
[TestMethod]
public async Task AccountPerission_PermissionScope() {
//Arrange
//..code removed for brevity
_applicationUserManager
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
.ReturnsAsync(appUser);
//Act
var result = await controller.AjaxResetUserPermission(appUser.Id, 1);
//Assert
Assert.IsNotNull(result);
}
have this (model builder) code on our DbContext.cs
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
modelBuilder.Entity<ApplicationUser>().ToTable("ApplicationUser");
Everything works fine except for Authorization/User Roles.
After checking all tables I noticed that IdentityUserRoles table creates 4 columns: RoleId, UserId, IdentityRole_Id and ApplicationUser_Id.
I found out that, IdentityRole_Id and ApplicationUser_Id [Foreign Keys] are mapped or used instead of the RoleId and UserId [Primary Keys]. Unfortunately, identity (Id's) data were inserted in RoleId/UserId column and IdenityRole_Id/ApplicationUser_Id are NULL by default.
Please help.
My Code:
public class RqDbContext : DbContext
{
private const string ConnectionString = "RqDbContext";
public RqDbContext() : base(ConnectionString)
{
}
public static RqDbContext Create()
{
return new RqDbContext();
}
// ----------------------------------------------------------------------
// Data Tables
// ----------------------------------------------------------------------
public DbSet<Quote> Quotes { get; set; }
public DbSet<Booking> Bookings { get; set; }
public DbSet<CompanyAccount> CompanyAccounts { get; set; }
// ----------------------------------------------------------------------
// Security
// ----------------------------------------------------------------------
public DbSet<ApplicationUserExtend> ApplicationUserExtends { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
modelBuilder.Entity<ApplicationUser>().ToTable("ApplicationUser");
}
}
public partial class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
//custom+
public virtual ApplicationUserExtend Extend { get; set; }
}
public class ApplicationUserExtend
{
public ApplicationUserExtend()
{
}
[Key]
[Display(Name="Id")]
[XmlAttribute]
public int Id { get; set; }
[Display(Name="Account Id")]
[XmlAttribute]
public int AccountId { get; set; }
[Display(Name="Active Account Id")]
[XmlAttribute]
public int ActiveAccountId { get; set; }
}
public class RqInitializer : System.Data.Entity.DropCreateDatabaseAlways<RqDbContext>
{
protected override void Seed(RqDbContext context)
{
var testData = ReadTestData();
AddIdentityRoles(context, testData);
AddUsers(context, testData);
MvcUtil.SaveChanges(context);
}
private void AddUsers(RqDbContext context, TestDataDo testData)
{
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
//Roles.Enabled("user","member");
var userIndex = 0;
foreach (var applicationUser in testData.ApplicationUsers)
{
var user = new ApplicationUser
{
UserName = applicationUser.UserName,
Email = applicationUser.Email,
PhoneNumber = applicationUser.PhoneNumber
};
if (userIndex > testData.ApplicationUserExtends.Count)
{
throw new Exception("Make sure you the number of rows in ApplicationUserExtends, matches the number of rows in Users");
}
user.Extend = new ApplicationUserExtend
{
AccountId = testData.ApplicationUserExtends[userIndex++].AccountId
};
userManager.Create(user, applicationUser.Password);
//set User Role
userManager.AddToRole(user.Id, applicationUser.Role);
//context.Users.Add(user);
}
context.SaveChanges();
}
private void AddIdentityRoles(RqDbContext context, TestDataDo testData)
{
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
foreach (var role in testData.IdentityRoles)
{
var identity = new IdentityRole(role.Name);
roleManager.Create(identity);
}
context.SaveChanges();
}
public static TestDataDo ReadTestData()
{
var xml = GetResource("Rq.Web.App_Specification.Rq-TestData.xml");
return XmlUtil.SerializeFromString<TestDataDo>(xml);
}
private static string GetResource(string file)
{
var assembly = Assembly.GetExecutingAssembly();
return ResourceUtil.GetAsString(assembly, file);
}
}
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<RqDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
The code below will fix IdentityUserRoles table Foreign Keys issue.
var user = modelBuilder.Entity<TUser>()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName).IsRequired();
modelBuilder.Entity<TUserRole>()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey})
.ToTable("AspNetUserLogins");
modelBuilder.Entity<TUserClaim>()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity<TRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name).IsRequired();
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
I found my answer here. Create ASP.NET Identity tables using SQL script!