ASP.Net Identity and Google Authentication Issue - asp.net

I have setup the project in Google and it gave me the appid and secret
I moved the id and secret to StartUp.Auth
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext<IdentityTestingDbContext>(IdentityTestingDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseGoogleAuthentication(
clientId: "*********************.apps.googleusercontent.com ",
clientSecret: "**************");
}
}
Here are the actions for external login, i am following Identity Sample Application (install-package Microsoft.AspNet.Identity.Samples -Pre).
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
var challenge = new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
return challenge;
}
// Used for XSRF protection when adding external logins
private const string XsrfKey = "XsrfId";
internal class ChallengeResult : HttpUnauthorizedResult
{
public ChallengeResult(string provider, string redirectUri)
: this(provider, redirectUri, null)
{
}
public ChallengeResult(string provider, string redirectUri, string userId)
{
LoginProvider = provider;
RedirectUri = redirectUri;
UserId = userId;
}
public string LoginProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var user = await UserManager.FindAsync(loginInfo.Login);
if (user == null)
{
user = new ApplicationUser
{
Email = loginInfo.Email,
UserName = loginInfo.DefaultUserName,
FirstName = string.Empty,
LastName = string.Empty
};
var result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return View("Error", result.Errors);
}
result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
if (!result.Succeeded)
{
return View("Error", result.Errors);
}
}
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaims(loginInfo.ExternalIdentity.Claims);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = false
}, identity);
return Redirect(returnUrl ?? "/");
}
I get redirected to google but here i am getting an error. Looks like i am missing something but can't figure it out. I have been searching for almost 3 hours and couldn't find any thing to help with this issue.
Do you see any thing that i may be doing wrong?
Why redirect url in the image below is http://localhost:58286/signin-google

Following helped
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
Fix 1:
The authorized redirect url needs to be http://localhost:58286/signin-google, for the google setup screen shot in the above question thread. This isn't the callback method inside the accounts controller.
Fix 2:
I needed to enable Google+ API as well which i didn't during the setup

Related

Registration with email confirmation ASP .NET Core

I'm learning ASP .NET and I'd want to make a simple registration/login page.
I've created my public class AccountController : Controller in this way:
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ILogger _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_logger = logger;
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
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(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("Utente loggato.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User bloccato.");
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Tentativo di login fallito.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public IActionResult Register(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[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)
{
_logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
//diallowed signin for self registration, email should be confirmed first
//await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");
return RedirectToConfirmEmailNotification();
}
AddErrors(result);
}
return View(model);
}
public async Task<IActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
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, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
}
Then I created Startup class and I've written the following method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Landing}/{action=Index}/{id?}");
});
}
When I register a new user I can see it into the database but I don't receive any mail and I can't log in with the new user if I try.
I've watched some tutorials on YouTube about it and read the Microsoft documentation. To me it seems correct compared with what I've done, but surely I have to modify something and I don't notice it.
EDIT: this is what I've done for EmailSender and NetcoreService class:
public class EmailSender : IEmailSender
{
private SendGridOptions _sendGridOptions { get; }
private INetcoreService _netcoreService { get; }
private SmtpOptions _smtpOptions { get; }
public EmailSender(IOptions<SendGridOptions> sendGridOptions,
INetcoreService netcoreService,
IOptions<SmtpOptions> smtpOptions)
{
_sendGridOptions = sendGridOptions.Value;
_netcoreService = netcoreService;
_smtpOptions = smtpOptions.Value;
}
public Task SendEmailAsync(string email, string subject, string message)
{
//send email using sendgrid via netcoreService
_netcoreService.SendEmailBySendGridAsync(_sendGridOptions.SendGridKey,
_sendGridOptions.FromEmail,
_sendGridOptions.FromFullName,
subject,
message,
email).Wait();
return Task.CompletedTask;
}
}
NetcoreService class:
public async Task SendEmailBySendGridAsync(string apiKey, string fromEmail, string fromFullName, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress(fromEmail, fromFullName),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email, email));
await client.SendEmailAsync(msg);
}

Too many redirects - Owin External Login with Facebook Asp.Net

I'm creating a web-api where I need to log people in using Facebook.
I'm following this guide.
Once I provide my credentials to Facebook, it should redirect to an Action but instead it says: "Too many redirects."
This is what I've got in my Startup.cs:
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
FacebookAuthenticationOptions facebookAuthOptions = new FacebookAuthenticationOptions()
{
AppId = "myAppId",
AppSecret = "myAppKey",
Provider = new FacebookAuthProvider()
};
app.UseFacebookAuthentication(facebookAuthOptions);
This is my FacebookAuthProvider: class:
public class FacebookAuthProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
context.Identity.AddClaim(new System.Security.Claims.Claim("ExternalAccessToken", context.AccessToken));
return Task.FromResult<object>(null);
}
}
This is my ChallengeResult class:
public class ChallengeResult : IHttpActionResult
{
public string LoginProvider { get; set; }
public HttpRequestMessage Request { get; set; }
public ChallengeResult(string loginProvider, ApiController controller)
{
LoginProvider = loginProvider;
Request = controller.Request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
Request.GetOwinContext().Authentication.Challenge(LoginProvider);
HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
return Task.FromResult<HttpResponseMessage>(response);
}
}
And this is the controller that I'm using to get the token from Facebook after user has logged in:
[HttpGet]
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
//[Route("ExternalLogin", Name = "ExternalLogin")]
public IHttpActionResult GetExternalLogin(string provider)
{
string redirectUri = string.Empty;
AppUserManager manager = new AppUserManager(new AppUserStore(new AppContext()));
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
UserLoginInfo loginInfo = new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey);
IdentityUser user = manager.Find(loginInfo);
bool hasRegistered = user != null;
ValidateRedirectUri(this.Request, ref redirectUri);
redirectUri = String.Format("{0}#external_access_token={1}&provider={2}&haslocalaccount={3}&external_user_name={4}",
redirectUri,
externalLogin.AccessToken,
externalLogin.LoginProvider,
hasRegistered.ToString(),
externalLogin.UserName);
return Redirect(redirectUri);
}
One thing that I'm really curious about, is that, if I uncomment this line:
[Route("ExternalLogin", Name = "ExternalLogin")]
And try to access that controller with that new route, it says that User (The one in the GetExternalLogin's if) is null.
This is the link that I'm using to test:
http://localhost:62887/api/ExternalAuth/GetExternalLogin?provider=Facebook&redirect_uri=http://localhost:62887/api/ExternalAuth/LoggedIn
And after the user has successfully logged in, this is the action that he's supposed to be redirected:
[HttpGet]
public IHttpActionResult LoggedIn()
{
return Ok(new { Message = "You've been successfully logged in! :)" });
}
I'm gonna kill myself, I finally got it working. I just had to update the NuGet Package from 2.1 to 3.1... >:/

Authentication and LoginPath for different areas in ASP.NET Core 2

ASP.NET Core 2
Help me to configure AddAuthentication for two routes: users (user accounts) and admin area.
For example, if user doesn't signed in and trying to enter /Account/Orders/ he'll be redirected to /Account/SignIn/.
But if someone trying access /Admin/Orders/ must be redireted to /Admin/Signin/
Have not found ay solution ATM.
Solved!
In admin area (controllers) we using Authorize attr. arg.: [Authorize(AuthenticationSchemes = "backend")] and that is.
BTW we are able to make any tuning by accessing HttpContext in AddCookie's options and events.
Configuration:
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.LoginPath = new PathString("/account/login/");
})
.AddCookie("backend", o =>
{
o.LoginPath = new PathString("/admin/account/login/");
});
#Alex's answer got me 90% of the way there.
In .Net 6, I'm using this https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0 approach to use Cookies without using the Username setup in identity.
Program.cs
var authentication = services.AddAuthentication(o =>
{
o.DefaultScheme = AuthenticationSchemes.FrontEnd;
});
authentication.AddCookie(AuthenticationSchemes.FrontEnd, o =>
{
o.LoginPath = CookieAuthenticationDefaults.LoginPath;
});
authentication.AddCookie(AuthenticationSchemes.BackEnd, o =>
{
o.LoginPath = new PathString("/admin/login/");
o.AccessDeniedPath = new PathString("/admin/accessdenied");
});
AppAuthenticationSchemes.cs
public class AuthenticationSchemes
{
public const string FrontEnd = "Frontend";
public const string BackEnd = "Backend";
public const string Either = FrontEnd + "," + BackEnd;
}
AccountController.cs
[AllowAnonymous]
public class AccountController : Controller
{
private readonly FrontEndSecurityManager _frontEndSecurityManager;
public AccountController(FrontEndSecurityManager frontEndSecurityManager)
{
_frontEndSecurityManager = frontEndSecurityManager;
}
[HttpPost(Name = "Login")]
public async Task<ActionResult> Login(LoginViewModel loginModel)
{
if (string.IsNullOrEmpty(loginModel.Username) ||
string.IsNullOrEmpty(loginModel.Password))
{
ModelState.AddModelError("form", "Please enter Username and Password");
return RedirectToAction("Login", "Account");
}
var loginResult = await _frontEndSecurityManager.ValidateCredentials(loginModel.Username, loginModel.Password);
if (!loginResult.IsSuccess)
{
this.AddFlash(FlashMessageType.Danger, "UserName or Password is incorrect");
return RedirectToAction("Login", "Account");
}
var identity = await _frontEndSecurityManager.CreateIdentityAsync(loginModel.Username, loginResult);
await _frontEndSecurityManager.SignInAsync(identity, HttpContext);
return RedirectToAction("Menu", "App");
}
}
FrontEndSecurityManager.cs
public class FrontEndSecurityManager
{
private readonly SignInApi _api;
private readonly AuthenticationOptions _authenticationOptions;
public FrontEndSecurityManager(SignInApi api, IOptions<AuthenticationOptions> authenticationOptions)
{
_api = api;
_authenticationOptions = authenticationOptions.Value;
}
public async Task SignInAsync(ClaimsIdentity identity, HttpContext httpContext)
{
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30),
IsPersistent = true,
IssuedUtc = DateTimeOffset.UtcNow
};
await httpContext.SignOutAsync(AuthenticationSchemes.BackEnd);
await httpContext.SignInAsync(AuthenticationSchemes.FrontEnd, new ClaimsPrincipal(identity), authProperties);
}
public async Task<LoginResult> ValidateCredentials(string username, string password)
{
if (_authenticationOptions.DemoUserEnabled)
{
if (string.Equals(username, "demo", StringComparison.InvariantCultureIgnoreCase))
{
var result = new LoginResult(StandardResults.SuccessResult, "")
{
Employee_Name = "Demo User",
Employee_Email = "DemoGuy#gmail.com",
Employee_Initials = "DG",
Employee_Type = "Regular",
Role1 = true,
Role2 = true,
Role3 = false
};
return result;
}
}
var apiRequest = new LoginRequest() { Username = username, Password = password };
var loginResult = await _api.LoginAsync(apiRequest);
if (loginResult.Success)
{
return loginResult.Data;
}
else
{
return LoginResult.Failure();
}
}
public async Task<ClaimsIdentity> CreateIdentityAsync(string username, LoginResult loginResult)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, AppRoles.User),
new Claim(ClaimTypes.Email, loginResult.Employee_Email, ClaimValueTypes.Email),
new Claim(ClaimTypes.GivenName, loginResult.GivenName),
new Claim(ClaimTypes.Surname, loginResult.Surname),
new Claim(AppClaimTypes.EmployeeType, loginResult.Employee_Type),
new Claim(AppClaimTypes.EmployeeInitials, loginResult.Employee_Initials),
new Claim(AppClaimTypes.Location, loginResult.Location.ToString(), ClaimValueTypes.Integer),
};
if (loginResult.Use_Checkin)
{
claims.Add(new Claim(ClaimTypes.Role, AppRoles.Checkin));
}
if (loginResult.Use_Pickup)
{
claims.Add(new Claim(ClaimTypes.Role, AppRoles.Pickup));
}
var identity = new ClaimsIdentity(claims, AuthenticationSchemes.FrontEnd);
return identity;
}
public void SignOutAsync(HttpContext httpContext)
{
httpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);
}
}
From here, you could easily extrapolate how you want the back-end authentication controller to work. Essentially something like
await HttpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);await HttpContext.SignInAsync(AuthenticationSchemes.BackEnd, new ClaimsPrincipal(identity), authProperties);
An example of using each policy would be
[Authorize(AuthenticationSchemes = AuthenticationSchemes.BackEnd)]
public IActionResult Secure()
{
return View("Secure");
}
or
[Authorize] // scheme not explicit, so Pr DefaultScheme = AuthenticationSchemes.FrontEnd is used
[Route("[controller]")]
public class OutgoingController : Controller
{
}

Identity Framework test if confirm email token is expired

Is it possible to test whether a confirm email token is expired using Identity Framework's UserManager? No matter what the error is, from the following:
var result = await UserManager.ConfirmEmailAsync(userId, code);
I get a generic "Invalid Token" error.
I found a way to parse the token for the date issued, which you can then check to see if is within the allowed timespan (default of 24hours if not specified).
Identity.cs
ApplicationUserManager
public IDataProtector Protector { get; set; }
public TimeSpan TokenLifespan { get; set; }
ApplicationUserManager Create()
// Explicitly set token expiration to 24 hours.
manager.TokenLifespan = TimeSpan.FromHours(24);
var dataProtectionProvider = options.DataProtectionProvider;
manager.Protector = dataProtectionProvider.Create("ASP.NET Identity");
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = manager.TokenLifespan
};
}
AccountController.cs
public async Task<ActionResult> ConfirmEmail(string Code, string UserId)
{
// Try/catch, validation, etc.
var tokenExpired = false;
var unprotectedData = UserManager.Protector.Unprotect(Convert.FromBase64String(Code));
var ms = new MemoryStream(unprotectedData);
using (BinaryReader reader = new BinaryReader(ms))
{
var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
var expirationTime = creationTime + UserManager.TokenLifespan;
if (expirationTime < DateTimeOffset.UtcNow)
{
tokenExpired = true;
}
}
// Do something if token is expired, else continue with confirmation
}
I found this blog post and Nkosi's answer to be extremely helpful, and if you want to go through the Identity source code, Microsoft has it here (The previous versions of Identity for MVC5 and lower here). Also, I apologize if its in poor form to answer a question that you, yourself put a bounty on, but I couldn't help but continue looking for a better solution.
I get around this by keeping/storing a copy of the generated token
public class ApplicationUser : IdentityUser {
public string EmailConfirmationToken { get; set; }
public string ResetPasswordToken { get; set; }
}
and associating it with the user in derived UserManager<ApplicationUser>.
public override async System.Threading.Tasks.Task<string> GenerateEmailConfirmationTokenAsync(string userId) {
/* NOTE:
* The default UserTokenProvider generates tokens based on the users's SecurityStamp, so until that changes
* (like when the user's password changes), the tokens will always be the same, and remain valid.
* So if you want to simply invalidate old tokens, just call manager.UpdateSecurityStampAsync().
*/
//await base.UpdateSecurityStampAsync(userId);
var token = await base.GenerateEmailConfirmationTokenAsync(userId);
if (!string.IsNullOrEmpty(token)) {
var user = await FindByIdAsync(userId);
user.EmailConfirmationToken = token; //<<< Last issued token
//Note: If a token is generated then the current email is no longer confirmed.
user.EmailConfirmed = false;
await UpdateAsync(user);
}
return token;
}
When the token is provided for confirmation, a search for the user via the token is done.
public static class ApplicationUserManagerExtension {
public static Task<string> FindIdByEmailConfirmationTokenAsync(this UserManager<ApplicationUser> manager, string confirmationToken) {
string result = null;
ApplicationUser user = manager.Users.SingleOrDefault(u => u.EmailConfirmationToken != null && u.EmailConfirmationToken == confirmationToken);
if (user != null) {
result = user.Id;
}
return Task.FromResult(result);
}
}
If the token matches a known user that indicates that it was a validly issued token.
Will then attempt to confirm token with User manager.
If confirmation fails then token has expired and an appropriate action is taken.
Else if the token confirmed, it is removed from associated user and thus invalidating the reuse of that token.
public override async System.Threading.Tasks.Task<IdentityResult> ConfirmEmailAsync(string userId, string token) {
var user = await FindByIdAsync(userId);
if (user == null) {
return IdentityResult.Failed("User Id Not Found");
}
var result = await base.ConfirmEmailAsync(userId, token);
if (result.Succeeded) {
user.EmailConfirmationToken = null;
return await UpdateAsync(user);
} else if (user.EmailConfirmationToken == token) {
//Previously Issued Token expired
result = IdentityResult.Failed("Expired Token");
}
return result;
}
A similar approach was implemented for password reset as well.
Here comes an .NET Core 2.1 adaption of the solution provided by #Nkosi :
ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string EmailConfirmationToken { get; set; }
public string ResetPasswordToken { get; set; }
}
Derived UserManager class
public class CustomUserManager : UserManager<ApplicationUser>
{
public CustomUserManager(IUserStore<ApplicationUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<ApplicationUser> passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<ApplicationUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override async Task<string> GenerateEmailConfirmationTokenAsync(ApplicationUser user)
{
/* NOTE:
* The default UserTokenProvider generates tokens based on the users's SecurityStamp, so until that changes
* (like when the user's password changes), the tokens will always be the same, and remain valid.
* So if you want to simply invalidate old tokens, just call manager.UpdateSecurityStampAsync().
*/
//await base.UpdateSecurityStampAsync(userId);
var token = await base.GenerateEmailConfirmationTokenAsync(user);
if (!string.IsNullOrEmpty(token))
{
user.EmailConfirmationToken = token; //<<< Last issued token
//Note: If a token is generated then the current email is no longer confirmed.
user.EmailConfirmed = false;
await UpdateAsync(user);
}
return token;
}
public override async Task<IdentityResult> ConfirmEmailAsync(ApplicationUser user, string token)
{
if (user == null)
{
return IdentityResult.Failed(new IdentityError {Description = "User not found."});
}
var result = await base.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
user.EmailConfirmationToken = null;
return await UpdateAsync(user);
}
else if (user.EmailConfirmationToken == token)
{
//Previously Issued Token expired
result = IdentityResult.Failed(new IdentityError { Description = "Expired token." });
}
return result;
}
}
UserManager Extension
public static class ApplicationUserManagerExtension
{
public static Task<string> FindIdByEmailConfirmationTokenAsync(this UserManager<ApplicationUser> manager, string confirmationToken)
{
string result = null;
ApplicationUser user = manager.Users
.SingleOrDefault(u => u.EmailConfirmationToken != null && u.EmailConfirmationToken == confirmationToken);
if (user != null)
{
result = user.Id;
}
return Task.FromResult(result);
}
}
Update:
The CustomUserManager has to be added to services in Startup.cs in ConfigureServices Method.
services.AddTransient<CustomUserManager>();
Without this, DependencyInjection fails.
You can use my controller.It's working mate.
public IActionResult ForgotPassword()
{
return View();
}
[HttpPost]
public async Task<IActionResult> ForgotPassword(string Email)
{
if (string.IsNullOrEmpty(Email))
{
return View();
}
var user = await _userManager.FindByEmailAsync(Email);
if (user == null)
{
return View();
}
var code =await _userManager.GeneratePasswordResetTokenAsync(user);
var callback = Url.Action("ResetPassword", "Account", new
{
token=code,
},Request.Scheme);
// send email
await _emailSender.SendEmailAsync(Email, "Confirm Password Reset", $"<a href='{callback}'>If you want to reset your password click please !</a>");
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
public IActionResult ForgotPasswordConfirmation() => View();
public IActionResult ResetPassword(string token)
{
if (token == null)
{
return View();
}
var model = new ResetPasswordModel()
{
Token = token,
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
return RedirectToAction("Index", "Home");
}
var result = await _userManager.ResetPasswordAsync(user, model.Token, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
return View(model);
}
public ActionResult ResetPasswordConfirmation() => View();

Store data in cookie with asp.net core identity

I'm using ASP.NET core identity with EF end I would like to store data related to the user in the authentication cookie.
This is how I used to do with ASP.NET 4.6 (appcontext is the data to store):
public static void IdentitySignin(AppContext appContext, string providerKey = null, bool isPersistent = false)
{
var claims = new List<Claim>();
// create *required* claims
claims.Add(new Claim(ClaimTypes.NameIdentifier, appContext.UserId.ToString()));
claims.Add(new Claim(ClaimTypes.Name, appContext.UserName));
// serialized AppUserState object
claims.Add(new Claim("appcontext" + EZA.Store.AppContext.Version, appContext.ToString()));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
// add to user here!
AuthenticationManager.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = isPersistent,
ExpiresUtc = DateTime.UtcNow.AddDays(7),
}, identity);
}
but now I'm using ASP.NET Identity with EF and I can't find a way to store some data in the cookie.
Use AddClaimsAsync or AddClaimAsync of UserManager<YourUserIdentity>. for exemple like this when you sign in your user:
public class AccountController : Controller
{
public UserManager<YourUserIdentity> UserManager { get; private set; }
public SignInManager<YourUserIdentity> SignInManager { get; private set; }
public AccountController(UserManager<YourUserIdentity> userManager,
SignInManager<YourUserIdentity> signInManager)
{
UserManager = userManager;
SignInManager = signInManager;
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.UserName);
await UserManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
var signInStatus = await SignInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: false);
if (signInStatus.Succeeded)
return RedirectToLocal(returnUrl);
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
// If we got this far, something failed, redisplay form
return View("Index", new LoginPageViewModel() { Login = model });
}
}
Before i read #aqua's answer(i have learned this way just now), i would say that you have two options:
1 - Overriding UserClaimsPrincipalFactory like below:
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AppClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("<claim name>", value)
});
return principal;
}
}
// register it
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
2- Using OnSigningIn event.
services.Configure<IdentityOptions>(opt =>
{
opt.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnSigningIn = async (context) =>
{
ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
identity.AddClaim(new Claim("<claim name>", value));
}
};
});

Resources