ASP.Net UserManager AddRoleTo not working - c#

with VS 2015 I've created a little MVC App with the default authentification stuff.
I've changed nothing but the code in the registration action of the account controller:
var context = new ApplicationDbContext();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
if (!roleManager.RoleExists("Administrator"))
{
roleManager.Create(new IdentityRole("Administrator"));
}
if (!roleManager.RoleExists("User"))
{
roleManager.Create(new IdentityRole("User"));
}
if (context.Users.Count() == 1)
{
userManager.AddToRole(user.Id, "Administrator");
} else
{
try
{
userManager.AddToRole(user.Id, "User");
}
catch (Exception ex)
{
throw new Exception("Error assigning role: " + ex.Message);
}
}
The first registrated user becomes an "Administrator". Though the "userManager.AddToRole(user.Id, "User");" doesn't throw an exception no following registrated user is assigned to that role (checked the table and also per Authorize(Role...).
I can't figure out what's wrong here.

I've figured it out. I've used generic email addresses with an "+" in the user name, e.g. user+01#mydomain.com. This doesn't cause trouble while registrating and throws no exception when try/catch the AddRoleTo method but in the result stack of the AddRoleTo appears an error that only chars and numbers are allowed for user name.

Related

Header Request too long in Error 400 on Chrome, but works fine on firefox

AccountController
This is my SignMeIn Claim function
private void SignMeIn(User user, bool isPersistent)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email),
new Claim(Global.CustomClaimTypes.UserId, user.UserId),
new Claim(Global.CustomClaimTypes.UserType, user.UserType),
new Claim(Global.CustomClaimTypes.UserModules, JsonConvert.SerializeObject(user.Rights)),
// ISSUE 001 - ONLY ON CHROME. WORKS FINE ON FIREFOX! - 05/06/2022
// Too heavy claim for store. Causing ERROR 400, data requested from the server is too long.
// Comment the code below to remove claim request from session!
new Claim(Global.CustomClaimTypes.UserStores, JsonConvert.SerializeObject(user.Stores)) // Original Code
};
var id = new ClaimsIdentity(claims, Global.AuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, id);
}
I'm using claims to save all user data into its current session, and can easily access Global variables and functionalities.
It work's fine calling out in Login Function
public ActionResult Login(LoginViewModel model, string returnUrl)
{
try
{
if (ModelState.IsValid)
{
var user = _userService.VerifyUser(model.InputUserID, ClsRijndael.EncryptRijndael(model.InputPassword));
if (user != null)
{
SignMeIn(user, model.RememberMe);
// Calling the fetch function - 05/06/2022
FetchStore(user);
//_sessionService.Set<string>(Global.SessionKeys.UserImage, user.ImageData);
if (user.UserType == "PCP")
{
return RedirectToAction("Main", "PCP");
}
else if (user.UserType == "SIC")
{
return RedirectToAction("Main", "SIC");
}
else if (user.UserType == "CCP")
{
return RedirectToAction("Main", "CCP");
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View(model);
}
catch (AuthenticationException)
{
ModelState.AddModelError("", "There is a problem with your account, please contact your administrator.");
return View(model);
}
catch (System.Exception)
{
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
}
But if I run on Chrome, it gives the Error 400 on specific that has quite a load of data.
CHROME ERROR
BUT IT WORKS FINE ON FIREFOX
Solutions I've tried:
clearing all the cookies and it doesn't seem to work.
adding a new claim outside the controller, but I haven't seen the fitting article for it so far.
web.server maxRequestLength = 'maximum amount'

Confirm Email in asp.net core web api

This API is intended for a mobile application. The goal is to let the user confirm the email upon registration. When the user registers, a confirmation link is generated and sent over the email. I've done it the same way in a MVC project, it worked fine, but in a Web API project looks like it ain't gonna cut.
Now when the user clicks that link, the respective action method should be hit and do the job.
The only problem is, the ConfirmEmail action method is just not getting triggered when clicking the confirmation link although it looked fine.
Here are the main configurations which might help
MVC service configuration
services.AddMvc(options =>
{
options.EnableEndpointRouting = true;
options.Filters.Add<ValidationFilter>();
})
.AddFluentValidation(mvcConfiguration => mvcConfiguration.RegisterValidatorsFromAssemblyContaining<Startup>())
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
Identity Service
public async Task<AuthenticationResult> RegisterAsync(string email, string password)
{
var existingUser = await _userManager.FindByEmailAsync(email);
if(existingUser != null)
{
return new AuthenticationResult { Errors = new[] { "User with this email address exists" } };
}
// generate user
var newUser = new AppUser
{
Email = email,
UserName = email
};
// register user in system
var result = await _userManager.CreateAsync(newUser, password);
if (!result.Succeeded)
{
return new AuthenticationResult
{
Errors = result.Errors.Select(x => x.Description)
};
}
// when registering user, assign him user role, also need to be added in the JWT!!!
await _userManager.AddToRoleAsync(newUser, "User");
// force user to confirm email, generate token
var token = await _userManager.GenerateEmailConfirmationTokenAsync(newUser);
// generate url
var confirmationLink = _urlHelper.Action("ConfirmEmail", "IdentityController",
new { userId = newUser.Id, token = token }, _httpRequest.HttpContext.Request.Scheme);
// send it per email
var mailresult =
await _emailService.SendEmail(newUser.Email, "BingoApp Email Confirmation",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(confirmationLink)}'>clicking here</a>.");
if (mailresult)
return new AuthenticationResult { Success = true };
else
return new AuthenticationResult { Success = false, Errors = new List<string> { "Invalid Email Address"} };
}
Controller
[HttpPost(ApiRoutes.Identity.Register)]
public async Task<IActionResult> Register([FromBody] UserRegistrationRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new AuthFailedResponse
{
Errors = ModelState.Values.SelectMany(x => x.Errors.Select(xx => xx.ErrorMessage))
});
}
// register the incoming user data with identity service
var authResponse = await _identityService.RegisterAsync(request.Email, request.Password);
if (!authResponse.Success)
{
return BadRequest(new AuthFailedResponse
{
Errors = authResponse.Errors
});
}
// confirm registration
return Ok();
}
[HttpGet]
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return null;
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return null;
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
await _emailService.SendEmail(user.Email, "BingoApp - Successfully Registered", "Congratulations,\n You have successfully activated your account!\n " +
"Welcome to the dark side.");
}
return null;
}
Your _urlHelper.Action(..) looks a bit suspicious to me.
I'm not sure you should pass the full controller name, that is, including the actual word controller.
Try _urlHelper.Action("ConfirmEmail", "Identity", instead.
As a tip: I try to avoid magic strings like these by using nameof(IdentityController) because it will return the controller name without the controller postfix.

.NET Core unit test how to Mock HttpContext RemoteIpAddress?

I have a .NET Core 3.1 project using Identity. For the Login page handler I have added a line of code that after a user logs in, it updates a users location based on their IP address:
_locationRepository.UpdateUserLocationAsync(HttpContext.Connection.RemoteIpAddress);
Full Code
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_locationRepo.UpdateUserLocation(HttpContext.Connection.RemoteIpAddress);
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
My problem is when writing the unit test, I don't know how to properly mock the HttpContext. I keep getting a null reference exception regardless of what I have tried.
var httpContext = new Mock<HttpContext>();
httpContext.Setup(x => x.Connection.RemoteIpAddress).Returns(new IPAddress(16885952));
How do I mock the RemoteIpAddress?
Xunit + FakeItEasy
var mockHttpContextAccessor = new Fake<IHttpContextAccessor>();
var httpContext = new DefaultHttpContext()
{
Connection =
{
RemoteIpAddress = new IPAddress(16885952)
}
};
mockHttpContextAccessor.CallsTo(x => x.HttpContext)
.Returns(httpContext);
var extractedIpAddress = IpExtractor.GetIpAddress(httpContext);
//assert 192.168.1.1
Here is my solution
public static void MockIpAddress(this ControllerBase controller)
{
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext()
{
Connection =
{
RemoteIpAddress = new System.Net.IPAddress(16885952)
}
}
};
}
The setup you have won't match any call. You need to mock the Connection as well:
var connectionMock = new Mock<Microsoft.AspNetCore.Http.ConnectionInfo>(MockBehavior.Strict);
connectionMock.SetupGet(c => c.RemoteIpAddress).Returns(new IPAddress(16885952));
var httpContext = new Mock<HttpContext>(MockBehavior.Strict);
httpContext.SetupGet(x => x.Connection).Returns(connectionMock.Object);
Set your MockBehavior to Strict to throw exceptions when calls are made to properties or methods that weren't set up properly.

why does FindByNameAsync always return null?

trying to authenticate a token through a post request, user always returns null when i check via the username.
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.UserName); // user always equals null here
if (user !=null && await _userManager.CheckPasswordAsync(user,model.UserName))
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySuperSecureKey"));
var token = new JwtSecurityToken(
issuer: "http://blah.com",
audience: "http://blah.com",
expires: DateTime.UtcNow.AddHours(1),
claims: claims,
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
wondering if the problem is coming from how i seeded my database with this dummy data class?
public class DummyData
{
public static void Initialize(IApplicationBuilder app, IServiceProvider serviceProvider)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<HealthContext>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
context.Database.EnsureCreated();
//context.Database.Migrate();
// Look for any ailments
if (context.Ailments != null && context.Ailments.Any())
return; // DB has already been seeded
var ailments = GetAilments().ToArray();
context.Ailments.AddRange(ailments);
context.SaveChanges();
var medications = GetMedications().ToArray();
context.Medications.AddRange(medications);
context.SaveChanges();
var patients = GetPatients(context).ToArray();
context.Patients.AddRange(patients);
context.SaveChanges();
if (!context.Users.Any())
{
ApplicationUser user = new ApplicationUser()
{
Email = "test#test.com",
SecurityStamp = Guid.NewGuid().ToString(),
UserName = "testTest"
};
userManager.CreateAsync(user, "Password123");
context.Users.Add(user);
context.SaveChanges();
}
}
}
After I seed my database i can see my the user created in the fields, its defo there, so not understanding what seems to be the issue here!
The first thing is to check the database to confirm whether your user is successfully created . There are several issues you can check firstly :
For userManager.FindByNameAsync confirm that you are typing the AndyCosStav , not the email .
The CheckPasswordAsync returns a flag indicating whether the given password is valid for the specified user.
public virtual System.Threading.Tasks.Task<bool> CheckPasswordAsync (TUser user, string password);
The second parameter should be the password , i notice you are checking with username :
if (user !=null && await _userManager.CheckPasswordAsync(user,model.UserName))
Password123 may not match the defalut ASP.NET Core identity password
restrict , you must have at least one non alphanumeric character.

PasswordHasher Always Fails Validation

I'm implementing a policy where passwords cannot be reused and have the structure in place, however I'm trying to work out how to check this against the PasswordHasher, I always get a failed match.
Please help on this..
try
{
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
ApplicationUser cUser = await store.FindByNameAsync(model.UserName);
if (cUser == null)
{
ModelState.AddModelError("", "User ID is not correct, please check and try again.");
return BadRequest(ModelState);
}
else
{
/// Need to check the new password is already entered recently
DataController dataController = new DataController();
string[] pwds = dataController.CheckIfNewPasswordSameAsLastFivePassword(model.UserName);
if (pwds != null)
{
foreach (string pwd in pwds)
{
PasswordVerificationResult result1 = UserManager.PasswordHasher.VerifyHashedPassword(pwd, model.UserIdentifier);
if (result1 == PasswordVerificationResult.Success)
{
ModelState.AddModelError("", "Please choose a password that you have not used before");
return BadRequest(ModelState);
}
}
}
//Send new password in model.UserIdentifier
await store.SetPasswordHashAsync(cUser, UserManager.PasswordHasher.HashPassword(model.UserIdentifier));
await store.UpdateAsync(cUser);
ApplicationUser userdetails = new ApplicationUser();
userdetails = dataController.GetUsersDetails(model.UserName);
if (userdetails.Email != null)
{
await ResetORForgotPasswordSendEmail(userdetails.FirstName, userdetails.UserName, userdetails.Email);
}
}
}
catch (Exception ex)
{
Logger.Error(ex.Message, ex);
}
return Ok();
}
VerifyHashedPassword is going to compare the password with the user's current password. What you should be doing instead is simply hash the new password and do a simple string compare to your list of old password hashes:
if (pwds.Contains(UserManager.PasswordHasher.HashPassword(model.NewPassword)))
{
ModelState.AddModelError("NewPassword", "You've used that password before.");
}

Categories

Resources