I am using Arvixe as my web host and have created a .NET 4.5 MVC app with individual user accounts. I created an ADO.NET Entity Data Model database first named "SecurityModel", updated my ApplicationDbContext to "joerdie_securityEntities", and switched my connection string to the following:
<add name="joerdie_securityEntities" connectionString="Data Source=localhost;Initial Catalog=joerdie_security;Integrated Security=false;User ID=myID;Password=myPassword" providerName="System.Data.SqlClient" />
I did this so that I could use my own SQL server db also hosted with Arvixe. When I deploy this base application, I am able to create a new user without an issue, and new users are entered into the SQL server database. However, when I try to create a new user in localhost the user creation fails in the following method during the declaration of "result".
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
try
{
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
// 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 RedirectToAction("Index", "Home");
}
AddErrors(result);
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
An Exception is caught but is null, and I never make it to the following if statement. I am able to bring in other models to this exact SQL Server instance and they update just fine. How do I set this up to be able to debug on my localhost pointing to my SQL Server instance?
Edit: Adding Console output.
Exception thrown: 'System.Data.SqlClient.SqlException' in mscorlib.dll
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Request","time":"2018-03-24T02:31:06.2230673Z","tags":{"ai.operation.name":"POST Account/Register","ai.operation.id":"MJK7rNb0X7k=","ai.location.ip":"::1","ai.cloud.roleInstance":"joerdie-Desktop","ai.internal.sdkVersion":"web:2.2.0-738"},"data":{"baseType":"RequestData","baseData":{"ver":2,"id":"MJK7rNb0X7k=","name":"POST Account/Register","duration":"00:01:01.1920000","success":true,"responseCode":"200","url":"http://localhost:53015/Account/Register","properties":{"DeveloperMode":"true"}}}}
The thread 0x10ec has exited with code 0 (0x0).
Related
The default project template in Visual Studio 2017 contains a function in the ManageController for the logged in User to change their password.
Following a successfull password change the user is then automatically signed in again
await _signInManager.SignInAsync(user, isPersistent: false);
What is the purpose of this sign in?
The full action method is below:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (!changePasswordResult.Succeeded)
{
AddErrors(changePasswordResult);
return View(model);
}
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToAction(nameof(ChangePassword));
}
Inside of ChangePasswordAsync, there is a call to UpdatePasswordHash, which itself makes a call to UpdateSecurityStampInternal. The implementation of UpdateSecurityStampInternal is not important - what is important is that this (obviously) updates the SecurityStamp property of the user.
Looking into how SignInManager works, you'll see that SignInAsync ends up with a call to UserClaimsPrincipalFactory's CreateAsync method, which itself calls into GenerateClaimsAsync. Inside of this implementation, you'll see the following:
if (UserManager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}
This means that after changing a password, the existing SecurityStampClaimType value will have changed. Reissuing a sign-in action ensures that a new ClaimsIdentity is created, which includes the new value of SecurityStamp.
This may not be the only reason for this sign-in action, but it does appear to be a reason.
I am creating a new application in ASP.NET Core 2.0 with Ddentity. I have enabled the Facebook Authentication according to the docs.
The flow that is running right now is the following:
When a new user authenticates from facebook the applicatiion asks for an email in order to be used for the application
When the user provides the email a user is created in my dbo.AspNetUsers table with the email provided by the user and a new line is created in my dbo.AspNetUserLogins with the reference to the provider, the providerKey and the userId.
At that point I can see that in my database all rows have correct data.
Now when the user logs out and tries to login again with facebook at some point the application checks if the user can login with the providers credentials through the:
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
At that point my result comes as NotAllowed (attribute IsNotAllowed = true) and it asks the user to enter a new email (Like the original flow for a new user). Of course then the email is already registered and the user can't create a new account as he shouldn't have to.
My controller for the ExternalLoginCallback is the default implementation of the project.
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
}
Can anyone help me find what am I missing or what should I see in order to get the answer to this? Thanks you in advance.
IsNotAllowed is set to true when you have set either RequireConfirmedEmail or RequireConfirmedPhoneNumber to true on SignInOptions (usually configured as part of AddIdentity) but the email address or phone number has not been confirmed.
If you don't want to require a confirmed email or phone number for external logins, you can set the relevant properties when creating your IdentityUser derived class after its first creation. e.g.:
var identityUser = new IdentityUser
{
// ...
EmailConfirmed = true,
PhoneNumberConfirmed = true
};
Otherwise, if you do want the verification process to occur, you can follow the guide in the ASP.NET Core docs for the email address side of things.
You can see how all this works in the source code. You end up in PreSignInCheck:
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
The CanSignInAsync implementation looks like this (with logging removed):
public virtual async Task<bool> CanSignInAsync(TUser user)
{
if (Options.SignIn.RequireConfirmedEmail &&
!(await UserManager.IsEmailConfirmedAsync(user)))
{
return false;
}
if (Options.SignIn.RequireConfirmedPhoneNumber &&
!(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
return false;
}
return true;
}
I'm using ASP.NET Identity, and I have the basic Forgot Password/Reset Password functionality in place.
When you fill out the form that you forgot your password, it creates a reset token using _userManager.GeneratePasswordResetTokenAsync(user)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
return View("ForgotPasswordConfirmation");
}
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
I noticed that the only validation the Reset Password page has is to check if the code is null, rather than also checking to see if it's still valid or not expired.
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null)
{
if (code == null)
{
throw new Exception("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
It doesn't actually check to see if the token is valid until you attempt to reset your password and it calls _userManager.ResetPasswordAsync(user, model.Code, model.Password)
I'd like to be able to validate that the code is still valid when they hit the Reset Password page to display a message to the user, and not after they attempt to reset their password.
Is there a way to check if it's valid?
Following code works to verify if the reset token is valid:
1. Create code and send it to user
var code = await this._userManager.GeneratePasswordResetTokenAsync(user);
2. Verify token
[HttpGet]
public async Task<IActionResult> ResetPassword(string UserId, string token)
{
...
ApplicationUser user = //get user;
if(!await this._userManager.VerifyUserTokenAsync(user,this._userManager.Options.Tokens.PasswordResetTokenProvider, "ResetPassword", token)){
ViewBag.Message = this._localizer["tokenNotValid"].Value;
}
...
}
See UserManager code: https://github.com/aspnet/Identity/blob/rel/2.0.0/src/Microsoft.Extensions.Identity.Core/UserManager.cs#L29
If you check the UserManager.ResetPasswordAsync(...) method, tracing throug to the VerifyUserTokenAsync method, which simply does:
// Make sure the token is valid
var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user);
You can just do this yourself as well, knowing that:
You can ask the DI Framework (e.g. via your controller constructor) for the token provider for your situation;
The purpose is just the hardcoded "ResetPassword" string;
The token is the code the user is using;
The user you should be able to get depending on how your view, e-mail, url, and whatever is set up (the default examples don't cover this I think, but you can easily put the user.Id in the "forgot password url" before the token itself, and extract it when you need it).
Then you can just call ValidateAsync yourself and adjust the view accordingly.
In my ASP.NET Core 1.1.1 app developed on VS2017 Ver 15.3.3, I'm using Account confirmation and password recovery in ASP.NET Core and MailKit to implement the above article's functionality but I'm getting the following error:
Note:
The error occurs at line await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false); of SendEmailAsync(...) method below and
at line await _emailSender.SendEmailAsync(model.Email, "Confirm your account", $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>"); of the Register(...) post method also shown below:
ERROR
SocketException: No such host is known
MessageServices.cs class
public class AuthMessageSender : IEmailSender, ISmsSender
{
public async Task SendEmailAsync(string email, string subject, string message)
{
// Plug in your email service here to send an email.
//return Task.FromResult(0);
var emailMessage = new MimeMessage();
//You can if required (and I have done in my code) set the LocalDomain used when communicating with the SMTP server
//This will be presented as the origin of the emails. In my case I needed to supply the domain so that our internal testing SMTP server would accept and relay my emails.
//We then asynchronously connect to the SMTP server. The ConnectAsync method can take just the uri of the SMTP server or as I’ve done here be overloaded with a port and SSL option. For my case when testing with our local test SMTP server no SSL was required so I specified this explicitly to make it work.
emailMessage.From.Add(new MailboxAddress("MyName", "MyEmail#MyDomain.com"));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart("plain") { Text = message };
using (var client = new SmtpClient())
{
client.LocalDomain = "smtp.MyDomain.com";
await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
Register post method in AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
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 https://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action(nameof(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);
}
UPDATE
Not certain, but the error may be related to me not using my email host info correctly:
I'm using a website/email hosting company DiscountASP.NET's webmail feature. Their SMTP Server name for each subscriber is smtp.YourDomainName.com. And hence, in the SendEmailAsync(...) method above, I'm using client.LocalDomain = "smtp.MyDomain.com";
For MailKit implementation I'm following Sending email via a SMTP server section of this article.
Open your cmd and write ipconfig /all for windows and search for your hostname of your PC
then enter your Hostname in this function
using (var client = new SmtpClient())
{
client.LocalDomain = "smtp.MyDomain.com";
await client.ConnectAsync("YourHostName", 25,false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
I've been learning MVC and got to this part:
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
UserManager.Delete(user);
//myDBLoginDetails ent = new myDBLoginDetails();
//ent.uspInsertUser(user.UserName, model.Password, model.FirstName, model.LastName, user.Email);
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// 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 RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
I thought everything was going smoothly and I successfully registered\saved users to the SQL Server db (no MDF file). To continue testing and such I remmed out the code to register\insert the users and deleted the table and recreated it so I could eventually start from scratch.
But then when I tried to re-register a user so I could step through the code to learn what all is going on I discovered my user(s) are still on my PC as I got the messages:
Name tempUser is already taken.
Email 'tempUser#email.com' is already taken.
I tried to delete the user (using UserManager.Delete(user);) but got the message:
[InvalidOperationException: The object cannot be deleted because it
was not found in the ObjectStateManager.]
I searched for an explanation and resolution but have failed, and only discovered there may be other\better ways to register\edit\delete users and frustration eventually set in.
So, I guess I need a couple things solved. I have a LoginDetalsEDmodel.edmx so am I still correct to use a Stored Proc or do I use the Framework? If the Framework then how do I do that?
And.... how do I get rid of the user(s) that are still on my computer (which are gone from the DB)?
I hope I explained this well enough as I'm still learning......