confirmation email in mvc5 - asp.net-mvc

i am new to mvc and i am trying to make confirmation email on registration in mvc5. i have followed sometutorials. lots of them use sendgrid but i want to use smtp. i have done the tutorials step by step by i cant get email after registration. i cant find the problem becuase i have done what i was read in tutorials.
any help is appritiated.
in identityConfig.cs
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
//return Task.FromResult(0);
// Credentials:
var credentialUserName = "[myEmail]";
var sentFrom = "[myEmail]";
var pwd = "[myPassword]";
// Configure the client:
System.Net.Mail.SmtpClient client =
new System.Net.Mail.SmtpClient("smtp.gmail.com");
client.Port = 587;
client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
// Create the credentials:
System.Net.NetworkCredential credentials =
new System.Net.NetworkCredential(credentialUserName, pwd);
client.EnableSsl = true;
client.Credentials = credentials;
// Create the message:
var mail =
new System.Net.Mail.MailMessage(sentFrom, message.Destination);
mail.Subject = message.Subject;
mail.Body = message.Body;
// Send:
return client.SendMailAsync(mail);
}
}
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
this.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
this.EmailService = new EmailService();
}
and in my AccontController.cs
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.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);
}
and i havnt change anything else. am i missing something?
thank you very much.

Related

ASP.NET MVC "SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);" return failure all the time

I've made some changes in my ExternalLogin actions. I added a new field to my user table, for the first name, and I added code as to request the first name from facebook. It could be the reason why I am getting the error. I will not post the app secret for facebook external login here, but you know it is present in my code. When I register a new user with facebook it works and keeps the user logged in, and adds the user to the database, also gets the correct first name. However When I log off and attempt to log in with facebook, it forces me to register instead of logging me in (it redirects to the register with facebook page, saying "you have successfully authenticated with facebook. Please enter a user name for this site below and click the Register button to finish logging in.")
Here is the code with the changes I made:
Inside the Startup.Auth class:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "2737863089761791",
AppSecret = "",
BackchannelHttpHandler = new HttpClientHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
foreach (var claim in context.User)
{
var claimType = string.Format("urn:facebook:{0}", claim.Key);
string claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
}
}
}
});
Inside my ExternalLoginCallback action in AccountController:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
var firstName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email, Name = firstName });
}
I figured it redirects me to the register page, because the line
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
returns failure inside ExternalLoginCallback and I don't know why. It must return success to get to the case:
case SignInStatus.Success
I had an error in my ExternalLoginConfirmation action, so i put some code in a try-catch block. It solved my problem, but I still don't know where the error was coming from:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Manage");
}
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Name, Email = model.Email , Name = model.Name};
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
try
{
var useraux = await UserManager.FindByEmailAsync(model.Email);
result = await UserManager.AddLoginAsync(useraux.Id, info.Login);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
Response.Write("Object: " + eve.Entry.Entity.ToString());
Response.Write(" " +
"");
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
Response.Write(ve.ErrorMessage + "" +
"");
}
}
//throw;
}
if (result.Succeeded)
{
//--
//await StoreFacebookAuthToken(user);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}

asp.net core 2.0 cookie authentication with session variable

I'm trying to implement asp.net core cookie-based authentication. So I have added the code below to my startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = new PathString("/login");
options.LoginPath = new PathString("/login");
options.SlidingExpiration = true;
});
and I'm sign-in using the code below
[HttpPost]
[ValidateAntiForgeryToken]
[Route("login")]
public async Task<IActionResult> Login(AuthViewModel authView)
{
if (ModelState.IsValid)
{
var (status, message, SigninUser) = await authentication.Authenticate(new User()
{
email = authView.Email,
pwd = authView.Password
});
if (status)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "App Member"),
new Claim(ClaimTypes.Email, SigninUser.email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, "cookie");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
scheme: CookieAuthenticationDefaults.AuthenticationScheme,
principal: principal,
properties: new AuthenticationProperties
{
IsPersistent = authView.RememberMe,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
});
HttpContext.Session.Set<User>("session_user", SigninUser);
if (Url.IsLocalUrl(authView.returnUrl))
return Redirect(authView.returnUrl);
else
return RedirectToAction("Index");
}
else
{
authView.Status = false;
authView.Message = message;
}
}
else
{
string message = string.Join(" | ", ModelState.Values.SelectMany(e => e.Errors).Select(v => v.ErrorMessage));
authView.Status = false;
authView.Message = message;
}
return View(authView);
}
This works fine. But when I keep the browser IDLE for like 30 minutes the "session_user" session variable get expired and the user still gets authenticated. How can I resolve this?
Also using cookie-based authentication can have a performance penalty?
Thanks

Token generated outside controller is too long and it's rejected by ConfirmEmail on Controller in MVC C#

I am updating my question as I have made some progress.
Thanks in advance for your support.
Question:
I am using GenerateEmailConfirmationTokenAsync to create a token outside the Controller (it's working fine), but somehow my token is longer than the ones created within the Controller using the GenerateEmailConfirmationTokenAsync and therefore the ConfirmEmail action rejects the token. (Error: Invalid Token).
I have tried Machinekey on webconfig, HttpUtility.UrlEncode, but I am still stuck. How to sort out the Invalid Token error on Controller ConfirmEmail?
Guys, can you help me please!
Thanks.
Here is my Code:
RegisterUser (outside Controller)
public async Task RegisterUserAsync()
{
var store = new UserStore<ApplicationUser>(db);
var UserManager = new ApplicationUserManager(store);
var query = from c in db.Customer
where !(from o in db.Users
select o.customer_pk)
.Contains(c.customer_pk)
select c;
var model = query.ToList();
if (query != null)
{
foreach (var item in model)
{
var user = new ApplicationUser { UserName = item.email, Email = item.email, customerId = item.customerId};
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id);
SmtpClient client = new SmtpClient();
MailMessage message = new MailMessage
{
IsBodyHtml = true
};
message.Subject = "Confirm Email";
message.To.Add(item.email1);
message.Body = "Please confirm your account by clicking here";
client.SendAsync(message, "userToken");
//Assign Role User Here
await UserManager.AddToRoleAsync(user.Id, "Client");
}
}
}
}
SendEmailConfirmation method (outside Controller)
public async Task<string> SendEmailConfirmationTokenAsync(string userID)
{
var store = new UserStore<ApplicationUser>(db);
var UserManager = new ApplicationUserManager(store);
var url = new UrlHelper();
var provider = new DpapiDataProtectionProvider("MyApp");
UserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
provider.Create("EmailConfirmation"));
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
string encodedCode = HttpUtility.UrlEncode(code);
string callbackUrl = "http://localhost/Accounts/ConfirmEmail?userId=" + userID + "&code=" + encodedCode;
return callbackUrl;
}
where db is
ApplicationdDbContext db = new ApplicationdDbContext();
ConfirmEmail within the Identity Controller (Accounts Controller) - I've created Accounts instead of Account controller but it's working fine.
//
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var confirmed = await UserManager.IsEmailConfirmedAsync(userId);
if (confirmed)
{
return RedirectToLocal(userId);
}
var result = await UserManager.ConfirmEmailAsync(userId, code); //Here I get the error (Token Invlaid, despite the token and userId being displayed)
if (result.Succeeded)
{
ViewBag.userId = userId;
ViewBag.code = code;
}
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(SetPasswordViewModel model, string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.AddPasswordAsync(userId, model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToLocal(userId);
}
ViewBag.userId = userId;
ViewBag.code = code;
AddErrors(result);
return View(model);
}
I have worked for hours in this code but until now I can't sort it out.
Thanks for any comments or solution. The reason for this approach is that I have to use task scheduler(I'm using fluentscheduler, which is working fine).

WebApi Facebook authentication with email address

I want to sign my users in using their email address from Facebook. I have configured my Facebook authentication:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Provider = new FacebookProvider()
};
app.UseFacebookAuthentication(facebookAuthenticationOptions);
I have overridden the Facebook provider to also return the email address:
public class FacebookProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
var accessTokenClaim = new Claim("ExternalAccessToken", context.AccessToken, "urn:facebook:access_token");
context.Identity.AddClaim(accessTokenClaim);
var extraClaims = GetAdditionalFacebookClaims(accessTokenClaim);
context.Identity.AddClaim(new Claim(ClaimTypes.Email, extraClaims.First(k => k.Key == "email").Value.ToString()));
context.Identity.AddClaim(new Claim("Provider", context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
var userDetail = context.User;
var link = userDetail.Value<string>("link") ?? string.Empty;
context.Identity.AddClaim(new Claim("link", link));
context.Identity.AddClaim(new Claim("FacebookId", userDetail.Value<string>("id")));
return Task.FromResult(0);
}
private static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new { fields = new[] { "email" } }) as JsonObject;
}
Everything works fine in MVC - in the LoginOwinCallback function, I am able to retrieve the user's email address as returned from Facebook. I am trying to achieve the same thing in WebApi using token authentication instead of external cookies. However, although I can see my provider adding the email claim to the response, when I call the AuthenticateAsync method in the following routine, the Email claim is not included.
private async Task<ExternalLoginInfo> GetExternalLoginInfoAsync()
{
var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
if (result == null || result.Identity == null) return null;
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
return new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
ExternalIdentity = result.Identity,
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
};
}
return null;
}
Any ideas what I am doing wrong?
For anyone else facing the same problem, the Email claim is available - just a bit hidden.
Firstly, to correctly retrieve the user's email address from Facebook, the authentication set-up in the ConfigureAuth method of Startup.Auth.cs should be:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Scope= {"email"},
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=email"
});
In the LoginOwinCallback method of the MVC AccountController class, the email address is found in the Email property of the ExternalLoginInfo object returned by var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();.
To retrieve the email address in the WebAPI AccountController class, you will need to cast the User.Identity object to the ClaimsIdentity type.
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
// get all of the claims
var claimsIdentity = User.Identity as ClaimsIdentity; // this cast exposes all of the claims returned by Facebook
// get the external login details
var externalLogin = ExternalLoginData.FromIdentity(claimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
var user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
var hasRegistered = user != null;
if (hasRegistered)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
var cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
var properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
AuthenticationManager.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
var claims = claimsIdentity?.Claims ?? externalLogin.GetClaims();
var identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationManager.SignIn(identity);
}
return Ok();
}
This means you can then use the FindFirst method on the Identity to find the email claim returned by Facebook.

ASP.NET Identity 2.1 - Password Reset Invalid Tokens

ASP.NET Identity is returning an 'Invalid token.' response when resetting a password for users.
I've tried the following:
URL Encode the code before sending email
URL Encode & Decode the code before and after
Copying the code to make sure it matches what was sent
Ensured my user email is confirmed (I heard that could be a problem)
Created a custom UserManager/Store etc.
This is my email code:
var user = await UserManager.FindByNameAsync(model.Email);
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { code }, "http");
var body = string.Format("Click here to reset your password: {0}", callbackUrl);
await UserManager.SendEmailAsync(user.Id, "Reset Password", body);
return View("~/Views/Account/Login.cshtml", model);
The generated URL:
http://localhost/Account/ResetPassword?code=XTMg3fBDDR77LRptnRpg7r7oDxz%2FcvGscq5Pm3HMe8RJgX0KVx6YbOeqflvVUINipVcXcDDq1phuj0GCmieCuawdgfQzhoG0FUH4BoLi1TxY2kMljGp1deN60krGYaJMV6rbkrDivKa43UEarBHawQ%3D%3D
Finally my reset code:
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
ModelState.AddModelError("","Invalid Password Please Try Again");
return View();
Inside the result is 1 error, Invalid token.
My create UserManager method:
public static CustomerUserManager Create(IdentityFactoryOptions<CustomerUserManager> options, IOwinContext context)
{
var manager = new CustomerUserManager(new CustomerUserStore(context.Get<CustomerDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<Customer>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<Customer, string>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
My Startup.Auth config:
app.CreatePerOwinContext(CustomerDbContext.Create);
app.CreatePerOwinContext<CustomerUserManager>(CustomerUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<CustomerUserManager, Customer, string>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (id.GetUserId())
)
}
});
List of tried solutions:
ASP.NET Identity 2 - UserManager.ConfirmEmail Fails In Production
aspnet identity invalid token on confirmation email
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset#reset
Asp.NET - Identity 2 - Invalid Token Error
aspnet identity invalid token on confirmation email
https://aspnetidentity.codeplex.com/discussions/544368
Thanks for any help with this problem.
You can try this code.
I shared this link: aspnet identity invalid token on confirmation email
var encodedCode= code.Base64ForUrlEncode();
var decodedCode= encodedCode.Base64ForUrlDecode();
public static class UrlEncoding
{
public static string Base64ForUrlEncode(this string str)
{
byte[] encbuff = Encoding.UTF8.GetBytes(str);
return HttpServerUtility.UrlTokenEncode(encbuff);
}
public static string Base64ForUrlDecode(this string str)
{
byte[] decbuff = HttpServerUtility.UrlTokenDecode(str);
return Encoding.UTF8.GetString(decbuff);
}
}

Resources