URL encoding password reset or email confirmation tokens
Asp.Net Identity 2.0 makes it really easy to validate an email address by using the GenerateEmailConfirmationTokenAsync method. However I recently hit an issue where the simplest path, from the same controller, in the same web app, on the same server could not round trip a validation token to confirm the email address.
Using nancy endpoint to send a code, and then validate the code.
public class ConfirmEmailModule : NancyModule
{
private readonly IdentityUserManager _userManager;
public ConfirmEmailModule(IdentityUserManager userManager) : base("confirmEmail")
{
_userManager = userManager;
Get["/", true] = async (x, ct) =>
{
var validateAttempt = this.Bind<ValidateEmail>();
var code = HttpUtility.UrlDecode(validateAttempt.Code);
var result = await _userManager.ConfirmEmailAsync(Guid.Parse(validateAttempt.UserId), code);
if (result.Succeeded)
{
ViewBag.ReturnUrl = validateAttempt.ReturnUrl;
return View["EmailValidated"];
}
ViewBag.Errors = result.Errors;
return View["ErrorValidatingEmail"];
};
Post["/{userId}", true] = async (x, ct) =>
{
var userId = Guid.Parse(x.userId);
string code = await _userManager.GenerateEmailConfirmationTokenAsync(userId);
var baseUrl = Context.Request.Url.SiteBase;
var encodedCode = HttpUtility.UrlEncode(code);
var callbackUrl = $"{baseUrl}/confirmEmail?userid={userId}&code={encodedCode}";
await _userManager.SendEmailAsync(userId,
"Confirm your email address",
"Please confirm your email address by clicking <a href=\"" + callbackUrl + "\">here</a>");
return HttpStatusCode.OK;
};
}
}
Using Postman to post a userId to the endpoint to send a confirmation URL created the following confimation code
frsv1AuYKgDTxcomdt27b/xLiGbzg/pv1k9e6X91B8enQUOIQn4sQ6w/vyZZ4QmIqHVHqMtuFXwbtSJoXKByKkTf7pM1EAftRwwJ2z3BZ/g61NLAhJQ5f8y8NcS+b1vR+/4Cs+yni5GiNElTqqtZVj99c1SY+J7Zsp+Ke/YjkVU3Q75eoghFWlZaCnyUAr29
And the URL encoded version to send in the email link becomes
frsv1AuYKgDTxcomdt27b%2FxLiGbzg%2Fpv1k9e6X91B8enQUOIQn4sQ6w%2FvyZZ4QmIqHVHqMtuFXwbtSJoXKByKkTf7pM1EAftRwwJ2z3BZ%2Fg61NLAhJQ5f8y8NcS%2Bb1vR%2B%2F4Cs%2Byni5GiNElTqqtZVj99c1SY%2BJ7Zsp%2BKe%2FYjkVU3Q75eoghFWlZaCnyUAr29
Now after clicking the emailed link, and then URL Decoding the validation code the code that is used for verification becomes
frsv1AuYKgDTxcomdt27b/xLiGbzg/pv1k9e6X91B8enQUOIQn4sQ6w/vyZZ4QmIqHVHqMtuFXwbtSJoXKByKkTf7pM1EAftRwwJ2z3BZ/g61NLAhJQ5f8y8NcS b1vR /4Cs yni5GiNElTqqtZVj99c1SY J7Zsp Ke/YjkVU3Q75eoghFWlZaCnyUAr29
Where all '+' chars in the original code have been replaced with spaces. Therefore there is an issue with directly URL encoding the Base64 string into a percent-encoded URL safe string. Hoever the wikipedia article on Base64 states that there is a URL safe variant of Base64, which does not use the percent encoding. A solution to creating URL safe Base64 string has been provided on Stack Overflow.
Using this sample, we can create a utility to make the Base64 codes URL safe, wihtout needing to generically URL encode a string.
public static class Base64Utility
{
static readonly char[] padding = { '=' };
public static string UrlSafeEncode(this string base64String)
{
return base64String.TrimEnd(padding).Replace('+', '-').Replace('/', '_');
}
public static string UrlSafeDecode(this string urlSafeBase64String)
{
string base64String = urlSafeBase64String.Replace('_', '/').Replace('-', '+');
switch (urlSafeBase64String.Length % 4)
{
case 2: base64String += "=="; break;
case 3: base64String += "="; break;
}
return base64String;
}
}
By using this encode utility method, the encoded code that is URL safe for the activation link becomes
frsv1AuYKgDTxcomdt27b_xLiGbzg_pv1k9e6X91B8enQUOIQn4sQ6w_vyZZ4QmIqHVHqMtuFXwbtSJoXKByKkTf7pM1EAftRwwJ2z3BZ_g61NLAhJQ5f8y8NcS-b1vR-_4Cs-yni5GiNElTqqtZVj99c1SY-J7Zsp-Ke_YjkVU3Q75eoghFWlZaCnyUAr29
and successfully decodes back to the original code generated by Asp.Net Identiy.
* flickr photo by Gruenewiese86 https://flickr.com/photos/gruenewiese/27682943560 shared under a Creative Commons (BY-ND) license