This question tends to come up a lot on the forum so I have decided to write an article explaing how to accomplish this in Umbraco 9+.
The flow is fairly standard:
- User submits their email
- If the user exists, we generate a token for them using
IMemberManager
GeneratePasswordResetTokenAsync(member)
- The token is sent to the member via email
- The member clicks the link, opening a form with the token sent above.
- Member submits the form with new password
- We reset the password using
IMemberManager
ResetPasswordAsync
ORChangePasswordWithResetAsync
We will start by creating a controller to handle all the posts etc.
MemberSurfaceController.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Website.Controllers;
namespace Controllers
{
public class MemberSurfaceController : SurfaceController
{
private readonly IMemberService _memberService;
private readonly IMemberManager _memberManager;
private readonly IMemberMailService _mailService;
public MemberSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider,
IMemberService memberService, IMemberManager memberManager,IMemberMailService mailService) : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_memberService = memberService;
_memberManager = memberManager;
_mailService = mailService;
}
[HttpPost]
public async Task<IActionResult> ForgotPasswordAsync(FormCollection form)
{
TempData["ResetSent"] = false;
if (!ModelState.IsValid)
{
return CurrentUmbracoPage();
}
//if the form has a NewPassword field, then process the password reset
if(form.Keys.Contains("NewPassword"))
{
var member = _memberService.GetById(Convert.ToInt32(form["userid"]));
var token = form["token"];
var newPassword = form["NewPassword"];
#region validate the form
var validPassword = await _memberManager.ValidatePasswordAsync(newPassword);
if (!validPassword.Succeeded)
{
ModelState.AddModelError("NoPass", "Password is not valid");
}
if (string.IsNullOrWhiteSpace(newPassword))
{
ModelState.AddModelError("NoPass", "You must enter a password");
}
if (newPassword != form["ConfirmPassword"])
{
ModelState.AddModelError("NoMatch", "passwords do not match");
}
if (form["token"][0].Replace(" ","+") != token)
{
ModelState.AddModelError("TokenInv", "Reset token is invalid");
}
if (!ModelState.IsValid)
{
TempData["Message"] = "Validation Error";
return CurrentUmbracoPage();
}
#endregion
#region reset the Umbraco password
var identityUser = _memberManager.FindByIdAsync(form["userid"]).Result;
var result = _memberManager.ResetPasswordAsync(identityUser, token, newPassword).Result;
if (!result.Succeeded)
{
TempData["Message"] = "Reset password error Error";
return CurrentUmbracoPage();
}
#endregion
//everything ok so redirect to the login page.
return Redirect("~/login");
}else{
var member = _memberService.GetByEmail(form["EmailAddress"]);
if (member != null)
{
var memberIdentity = await _memberManager.FindByIdAsync(member.Id.ToString());
// we found a user with that email so generate a token ....
var token = await _memberManager.GeneratePasswordResetTokenAsync(memberIdentity);
var encodedToken = !string.IsNullOrEmpty(token) ? HttpUtility.UrlEncode(token) : string.Empty;
// send email ....
await _mailService.SendResetPasswordAsync(member.Email,encodedToken);
TempData["ResetSent"] = true;
}
else
{
ModelState.AddModelError("ForgotPasswordForm", "Member not found");
}
}
return CurrentUmbracoPage();
}
}
}
We also need a form for the member to enter their email address, the form is dual purpose, if there are values for id and token in the querystring it displays the change password fields, otherwise it displays the email field.
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.ForgotPassword>
@using Controllers
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@{
Layout = "_LayoutBody.cshtml";
bool change = false;
var user = Context.Request.Query["id"];
var token = Context.Request.Query["token"];
if (!string.IsNullOrWhiteSpace(token))
{
change = true;
TempData["Message"] = "Reset password";
}
}
<section>
<div class="container">
<h3>@title</h3>
<div class="row">
<div class="col-10 offset-1">
@using (Html.BeginUmbracoForm<MemberSurfaceController>("ForgotPassword"))
{
<!-- If the change flag is true render the change password form -->
if (change)
{
<fieldset>
<div class="form-group ">
<label class="control-label col-sm-5">New password</label>
<div class="col-sm-7">
<div class="input-group" id="show_hide_password">
<input type="password" id="NewPassword" required minlength="10" name="NewPassword" placeholder="new password" class="form-control ltr" />
<div class="input-group-addon">
<a href=""><i class="fa fa-eye-slash" aria-hidden="true"></i></a>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-5">Confirm password</label>
<div class="col-sm-7">
<input type="password" id="ConfirmPassword" required minlength="8" name="ConfirmPassword" placeholder="confirm password" class="form-control ltr" />
</div>
</div>
<div class="form-group">
<input type="hidden" id="userid" name="userid" value="@user" />
<input type="hidden" id="token" name="token" value="@token" />
<input type="submit" value="Reset password" class="btn btn-danger" />
</div>
</fieldset>
}
else
{
@Html.Raw(TempData["Message"])
}
<div class="form-group">
@Html.ValidationSummary()
</div>
}
else
{
<fieldset class="form-group">
<div class="form-group">
<input required type="email" id="email" data-toggle="tooltip" name="email" placeholder="login email" class="form-control ltr" />
</div>
<div class="form-group">
@Html.ValidationSummary()
</div>
<div class="form-group">
<input type="submit" value="Send" class="btn btn-danger" />
</div>
</fieldset>
}
</div>
</div>
</div>
</section>
When a member enters an email address and submits the form, the controller method checks that a member exists with that email address and if there is it generates a password reset token and sends an email to that address which contains a link for the member to reset their password.
If the member has received the email and opens the link they will be presented with the change password form
This form posts back to the same controller method and changes the members password.
In this article I will explain how to configure the TinyMCE rich text editor in Umbraco v14
This is my dive into the new Umbraco 14 backoffice to create a Member EntityAction in order to send an email to the selected member.
Previously known as Tree Actions, Entity Actions is a feature that provides a generic place for secondary or additional functionality for an entity type. An entity type can be a media, document and so on.
In this blog post I explain how to implement an email validation flow for Member registration.