I have setup SendGrid for my user registration email confirmation in my .Net 5.0 app as per Microsoft's instructions here: http://go.microsoft.com/fwlink/?LinkID=532713
Everything works fine until the user clicks the confirmation link in their register confirmation email.
This issue is being caused by a stray amp in my confirmation link. I am trying to understand where it is coming from and how to remove it.
When the new user clicks 'Submit' on the Register.cshtml page they are successfully directed to the RegisterConfirmation.cshtml page and the email is received in their inbox.
Actual behavior:
The user clicks the link in the email and hits the ConfirmEmail page.
The user is redirected to /Index page.
The EmailConfirmed bool in the DB is not updated.
If I comment out the redirect to /Index in my controller, then I get a null value error shown below.
//if (userId == null || code == null)
//{
// return RedirectToPage("/Index", new { culture });
//}

However, the query tab shows that the userId and code are correct.

Expected behavior:
The user clicks the link in the email.
The EmailConfirmed bool in the DB IS updated.
The user sees the ConfirmEmail page with the success message.
ConfirmEmail.cshtml.cs
using System;
snip...
namespace MyApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ISharedCultureLocalizer _loc;
private readonly string culture;
readonly ConfirmEmailPageLocSourceNames _locSourceConfirmEmailPageNameReferenceLibrary = new ConfirmEmailPageLocSourceNames();
readonly SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();
public ConfirmEmailModel(UserManager<ApplicationUser> userManager, ISharedCultureLocalizer loc)
{
_userManager = userManager;
_loc = loc;
culture = System.Globalization.CultureInfo.CurrentCulture.Name;
}
[TempData]
public string StatusMessage { get; set; }
snip...
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
snip...
if (userId == null || code == null)
{
return RedirectToPage("/Index", new { culture });
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
var msg = _loc.GetLocalizedString("Unable to load user with ID '{0}'.", userId);
return NotFound(msg);
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
if (result.Succeeded)
{
var msg = _loc.GetLocalizedString("Thank you for confirming your email.");
TempData.Success(msg);
}
else
{
var msg = _loc.GetLocalizedString("Error confirming your email.");
TempData.Danger(msg);
}
return Page();
}
}
}
RegisterConfirmation.cshtml.cs
using Microsoft.AspNetCore.Authorization;
snip...
namespace MyApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IEmailSender _sender;
readonly RegisterConfPageLocSourceNames _locSourceRegisterConfPageNameReferenceLibrary = new RegisterConfPageLocSourceNames();
readonly SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();
public RegisterConfirmationModel(UserManager<ApplicationUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
public string Email { get; set; }
snip...
public async Task<IActionResult> OnGetAsync(string email)
{
PageTabTitle = _locSourceRegisterConfPageNameReferenceLibrary.GetLocSourcePageTabTitleNameReferenceForRegisterConfPage();
snip...
if (email == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
return Page();
}
}
}
Register.cshtml.cs
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
Debug.WriteLine("**************** " + code);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
Debug.WriteLine("**************** 1 *** " + code);
var callbackUrl = Url.Page("/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = user.Id, code = code, culture }, protocol: Request.Scheme);
var mailHeader = _loc.GetLocalizedString("Confirm your GatheringForGood email");
var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='{0}'>clicking here</a>.", HtmlEncoder.Default.Encode(callbackUrl));
await _emailSender.SendEmailAsync(Input.Email, mailHeader, mailBody);
Functions in EmailSender.cs
public Task SendEmailAsync(string email, string subject, string message)
{
return Execute(Options.SendGridKey, subject, message, email);
}
public Task Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("[email protected]", Options.SendGridUser),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
return client.SendEmailAsync(msg);
}
Update:
In the callback string from my logs it says
userId=362c17ae-7854-42fb-91c3-efb19cc875f2&code=
But the link received in gmail says
userId=362c17ae-7854-42fb-91c3-efb19cc875f2&code=
Tested in Postman and this is definitely the issue. I need the link in the email body to look like this. Notice there is no amp;
https://localhost:44305/en/Identity/Account/ConfirmEmail?userId=d62d4727-f6ce-493c-bcf3-eb85a50a914f&code=Q2ZESjhKbkE2NU5BVk85S2drRnMvV3VtZXBySVFlTHZrQlNvUU9xbUxrYWQ5NjFDV0NvZGY1eHVCK01SSHVIL3EwMjEwYk8rU1lLaHJ4UHF1VS84RjJQTThBWlY4VHZTcGcrQVpiZU9wWHFyWnlsVkFpSFVUV3lIMGJjaG14aFJKQkgxNjZoQkVNM3ZETnR2WHhoZmx0ZnhQR095azdDREJVZVdJN01CTTRCcFptejJvSURjNHloZHdxRDl0UCs0eEdic1NMK25wbnFqb0xhdHFoR3M3T3BkTElhbG5TVU9obTJaTFpvc0xUb0RINzM2UmFBTVlrakZWL2VsV0YvUEJSaE1HQT09
Fixed! I just had to remove the Html encoding from my mailBody string population in the end.
I replaced:
var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='{0}'>clicking here</a>.", HtmlEncoder.Default.Encode(callbackUrl));
With:
var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='" + callbackUrl + "'>clicking here</a>.");
Thank you for all your help.
it looks like the variable that has value is amp;code; not code. Do you have 2 ampersands somewhere by any chance? Yes you do -
b3a&code=Q2ZE
Figure out why your code is generating such erroneous link in the confirmation email
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With