Is it possible to use Asp.net Identity with autoincremental key fields? - c#

I am use my own EF database using long as the Id field. I have created all the entities and the relevant custom stores.
However when registering a new user, it is trying to call SetPasswordHashAsync before it has called CreateAsync, which means that it is trying to write to a database record that doesn't exist yet and of course it is throwing an error.
It looks like the system is expecting to know the Id field before it has been written to the DB but until then it cannot know the Id as it is set to be an auto-incremental field.
What am I missing? Is this even possible?
Here is the code from the AccountController (taken from the MVC project template)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Email, Email = model.Email };
var result = await userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// We do not want the user to be logged in as soon as they register. We want them have to go through the login process.
//await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// Send an email with this link
string code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await userManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return View("DisplayEmail");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and here is the code from UserManager (from Identity.core) that is called
public virtual async Task<IdentityResult> CreateAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException("user");
}
if (password == null)
{
throw new ArgumentNullException("password");
}
var result = await UpdatePasswordInternal(passwordStore, user, password).WithCurrentCulture();
if (!result.Succeeded)
{
return result;
}
return await CreateAsync(user).WithCurrentCulture();
}
So you can see that updating the password before calling createUser is the intended process, but how is that meant to work? That just seems wrong to me.

Related

ASP.NET Core External Identity Provider Login problem

I've tried to implement Google Login in my API, but the table "AspNetUserLogins" in SQL won't get populated.
public async Task<IActionResult> ExternalLoginCallback(string? returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null) {
return RedirectToAction(nameof(Login));
}
var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (signInResult.Succeeded)
{
return RedirectToLocal(returnUrl);
}
if (signInResult.IsLockedOut)
{
return RedirectToAction(nameof(ForgotPassword));
}
else
{
ViewData["ReturnUrl"] = returnUrl;
ViewData["Provider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginModel { Email = email });
}
}
On the "var signInResult", I get "failed" and I can't access the If statement. Can you please help me?
I've tried many different solutions, but none of them worked.
After testing, I found that Google and other three-party logins cannot be used like Asp.Net Core Identity, and can be used directly after modification.
When using Google provide.
we need add below code in controller
[HttpPost("google-login")]
public IActionResult GoogleLogin()
{
var properties = new AuthenticationProperties { RedirectUri = "/api/authentication/google-login-callback" };
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
[HttpGet("google-login-callback")]
public async Task<IActionResult> GoogleLoginCallback()
{
var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (!result.Succeeded)
{
return BadRequest("Failed to authenticate with Google.");
}
var user = new
{
Id = result.Principal.FindFirst(ClaimTypes.NameIdentifier).Value,
Email = result.Principal.FindFirst(ClaimTypes.Email).Value,
Name = result.Principal.FindFirst(ClaimTypes.Name).Value
};
// TODO: Create or update user account in your database.
return Ok(new { User = user });
}
2. And I replace the form tag in Login.cshtml like below.
3. Test Result
In Asp.Net Core Identity, we can change the code to api endpoint directly like below.
public async Task<IActionResult> LoginApi(string? returnUrl, InputModel? Input)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
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)
{
_logger.LogInformation("User logged in.");
return Redirect(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 Ok("Invalid login attempt.");
}
}
// If we got this far, something failed, redisplay form
return Ok("Invalid model.");
}

AmbiguousActionException when confirm e-mail in identity

I'm working on a web service to manage user registration..
I don't understand because obtain this:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Test.Api.Controllers.AccountsController.ConfirmEmail (Test.Api)
Test.Api.Controllers.AccountsController.ResetPassword (Test.Api)
This is my code
[HttpPost]
[AllowAnonymous]
[Route("register")]
public async Task<IActionResult> Register([FromBody]RegistrationViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<AppUser>(model);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
if (!result.Succeeded) return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState));
var token = await _userManager.GenerateEmailConfirmationTokenAsync(userIdentity);
var callbackUrl = Url.EmailConfirmationLink(userIdentity.Id, token, Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Confirm ", $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
await _appDbContext.Customers.AddAsync(new Customer { IdentityId = userIdentity.Id, Location = model.Location });
await _appDbContext.SaveChangesAsync();
return new OkObjectResult("Account created");
}
I done a helper for buld the callbackUrl whit this code:
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string token, string scheme)
{
return urlHelper.Action(
action: nameof(AccountsController.ConfirmEmail),
controller: "Accounts",
values: new { userId, token },
protocol: scheme);
}
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return new BadRequestResult();
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded)
{
return new BadRequestResult();
}
await _emailSender.SendEmailAsync(user.Email, "Account confermato","Il tuo account รจ stato confermato");
return new OkObjectResult("Account confirmed");
}
public IActionResult ResetPassword(string code = null)
{
if (code == null)
{
throw new ApplicationException("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
how is it possible that these two methods ( ConfirmEmail(string userId, string token) and ResetPassword(string code = null)) can create ambiguity? the two fime are different
You've indicated in the comments on your question that AccountsController is decorated with the following attribute:
[Route("api/[controller]")]
This means that your ConfirmEmail and ResetPassword actions are using the same route, which is simply api/Accounts in this case. When this route is loaded up in the browser, both actions are candidates for processing the request and so you get an error as described in your question.
In order to fix this, there are a few options:
Add [Route("ConfirmEmail")] to ConfirmEmail and [Route("ResetPassword")] to ResetPassword (similar to what you've done with the Register action).
Add [Route("[action]")] to both ConfirmEmail and ResetPassword, where [action] is a placeholder for the action name.
Remove [Route("register")] from your Register action and update the controller's attribute to [Route("api/[controller]/[action]")]. You'd also need to remove [Route(...)] from any other actions you may have. Note that this approach is only viable if all your actions are routed according to the api/[controller]/[action] pattern.

How can I the userId before an account has been created?

I know the title is a bit of a oxymoron; however, I have a register form that also takes more details from the user such as first name, last name etc.
Inside the Register method of the account controller, I have added a Members object, populated it with the data, however, I need the GUID (UserId) of the account being registered to be stored as the "MemberId".
The user MUST have clicked register to get into this actionresult method but no userId is created until program flow has left.
How can I get the userId of the registered account as it returns null? Any help is appreciated. Sorry for the ambiguity.
public async Task<ActionResult> Register(RegisterForm model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.RegisterViewModel.Email, Email = model.RegisterViewModel.Email };
var result = await UserManager.CreateAsync(user, model.RegisterViewModel.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
var members = new Members()
{
MemberId = new Guid(User.Identity.GetUserId()),
// Obviously the above returns null because there is no userId yet.
FirstName = model.Members.FirstName,
LastName = model.Members.LastName,
PhoneNumber = model.Members.PhoneNumber,
Role = model.Members.Role
};
_context.Members.Add(members);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(model);
}
Assuming you are using EntityFramework, after calling UserManager.CreateAsync the property for user.Id will have been updated.
So you can use the user id like this:
MemberId = new Guid(user.Id)

UpdateAsync method is not work in web api?

I am create small demo for user registration using web api mvc c#.i am register Succeeded for user using 'Register' in web api.now i want to using this method also edit this user by id so how can do that i don't know any one know please let me know. i my code i will manage add/edit call in one method so i will first check the condition on id is null then add and id is not null then go for the edit but how can edit this record and role.
here this my method :
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
try
{
if (model.Id == "")
{
//here is for add user method
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email,PhoneNumber = model.PhoneNumber };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var UsersContext = new ApplicationDbContext();
var res = UsersContext.Users.Where(x => x.UserName == user.UserName).FirstOrDefault();
var UserId = res.Id;
await UserManager.AddToRoleAsync(UserId, model.UserRole);
return Ok(result);
}
return Ok(result);
}
else
{
//here i want write edit code
var UsersContext = new ApplicationDbContext();
var Team = UsersContext.Users.Find(model.Id);
Team.UserName = model.UserName;
Team.Email = model.Email;
Team.PhoneNumber = model.PhoneNumber;
IdentityResult result = await UserManager.UpdateAsync(Team); //here getting error.
return Ok(result);
}
return Ok("Done");
}
catch (Exception ex)
{
}
return Ok();
}
UPDATED:
First check whether you are passing values to all required field or not.
Keep this UserStore after your controller class calling braces {.
[Authorize]
public class AccountController : Controller
{
UserStore<IdentityUser> myUserStore = new UserStore<IdentityUser>(new
ApplicationDbContext());
//rest code
Then inside your Register() Post method, do like this bellow,
if (model.Id == "")
{
//Add new user code
}
else
{
var context = myUserStore.Context as ApplicationDbContext;
var Team = context.Users.Find(model.Id);
//change the field value what ever you want to update
context.Users.Attach(Team);
context.Entry(Team).State = EntityState.Modified;
context.Configuration.ValidateOnSaveEnabled = false;
await context.SaveChangesAsync();
return Ok("Updated");
}
Hope it helps :)

Implement the SendEmailAsync interface and send Email

I don't know how to invoke the SendEmailAsync function
Register Post page
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
$"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Email configuration Page
// this code was already there
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
//I got this code from some other site. I don't know how to use this together.
public class sendMail : IEmailSender // this line is written by me
{
public async Task SendEmailAsync(string email, string subject, string message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress("Joe Bloggs", "jbloggs#example.com"));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart("plain") { Text = message };
using (var client = new SmtpClient())
{
client.LocalDomain = "some.domain.com";
await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
}
how the write the class definition to send email.
Break point does not reach here, I think there is some problem with my implementation.
in Startup make sure you change this:
services.AddTransient<IEmailSender, AuthMessageSender>();
to
services.AddTransient<IEmailSender, sendMail>();
so that your implementaiton gets injected

Categories