I'm busy with the register functionality of my api and I have already implemented email confirmation which works fine. But there are a couple of problems that I am struggling with since I'm new to c# and .net core.
I want to implement a resend email functionality incase a user doesn't receive an email/doesn't accept in time, I have created a separate entity like this to store Confirmation tokens that .net generates by calling the UserManager.GenerateEmailConfirmationTokenAsync() method :
public class ConfirmationToken {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Token { get; set; }
public string? ResendToken { get; set; }
public bool? isUsed { get; set; }
public DateTime? CreatedAt { get; set; } = DateTime.Now;
public DateTime? ExpiredAt { get; set; } = DateTime.Now.AddHours(24);
public virtual User? User { get; set; }
public string UserId { get; set; }
}
This does store the token that .net generates along with the specific user Id but there is an issue with the token it stores.
For example, when logging out the token provided by the GenerateEmailConfirmationTokenAsync() method I would get something like this after encoding it (This is what gets stored in my Confirmation Token table):
CfDJ8MhQpKYkLedOlUKK9%2bU10PcPTorIiCUSmUQ9vyl48rr91cCZnkYEuKKZLf4rLt90Dag6OWpkfbjwyhV1FbGNGuUZ90pUL7vi%2fe1T0JaXoV8SSiyZx41lWHwkb71rLTl3xYaDd6Bq6MaFClUjAEfyoorXZZ3K9ddmKb6Byf28%2bKRBtt2vlzayqxNkZbl43thYM%2fuzn8oCwkD8fc%2fhByV0wFCSgkUQKUHv3FKf5n%2bNbG3%2b%2bpwczzsooqoGvuyUSjsvqA%3d%3d
But after I call my confirm endpoint which in turn calls the UserManager.ConfirmEmailAsync method (I do pass my decoded token here and it does confirm properly as well as set EmailConfirmed=true) I get a different token, (this is the url that contains userId and the token to actually confirm the email) which looks like this:
https://localhost:7050/api/Auth/ConfirmEmail?userId=cc985a22-53dd-4e69-a0fe-70b82b6a9925&confirmationToken=CfDJ8MhQpKYkLedOlUKK9%252bU10PcPTorIiCUSmUQ9vyl48rr91cCZnkYEuKKZLf4rLt90Dag6OWpkfbjwyhV1FbGNGuUZ90pUL7vi%252fe1T0JaXoV8SSiyZx41lWHwkb71rLTl3xYaDd6Bq6MaFClUjAEfyoorXZZ3K9ddmKb6Byf28%252bKRBtt2vlzayqxNkZbl43thYM%252fuzn8oCwkD8fc%252fhByV0wFCSgkUQKUHv3FKf5n%252bNbG3%252b%252bpwczzsooqoGvuyUSjsvqA%253d%253d
So this token I get here is different from the one I store in my database, If I wanted to find a token by it's value I would never get any response because I always get a different version of the token already in my database.
I've been struggling for a while but I can't seem to understand why, the actual email confirmation does work, but now I want to add a resend email feature with a new token (and also validate that the old token is infact invalid etc).
My code is as follows:
RegisterUser() - Service method Registers the user account and also generates a link to confirm account
public async Task<User> RegisterUser(UserRegisterRequest request) {
var userNameExists = await _userManager.FindByNameAsync(request.UserName);
var emailExists = await _userManager.FindByEmailAsync(request.EmailAddress);
var user = new User() {
UserName = request.UserName,
Email = request.EmailAddress,
//EmailConfirmed = false
};
var result = await _userManager.CreateAsync(user, request.Password);
if (result.Succeeded) {
var confirmationToken = await _confirmationTokenService.GenerateConfirmationToken(user.Id);
// Points to an endpoint to confirm the email and passes the userId and token
var callback_url = "https://localhost:7050" + _urlHelper.Action("ConfirmEmail", "Auth",
new {
userId = confirmationToken.UserId,
confirmationToken = confirmationToken.Token
});
return user;
}
return null;
}
GenerateConfirmationToken(string userId) - Service method Generates a email token and inserts in DB along with userId
public async Task<ConfirmationToken> GenerateConfirmationToken(string userId) {
var user = await _userManager.FindByIdAsync(userId);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = HttpUtility.UrlEncode(token);
var emailConfirmationToken = new ConfirmationToken {
Token = encodedToken,
UserId = user.Id,
isUsed = true
};
// Save encoded token with associated userId to database table
await _unitOfWork.ConfirmationTokens.Add(emailConfirmationToken);
int result = _unitOfWork.Save();
return emailConfirmationToken;
}
ConfirmToken(string userId, string token) - Service method Confirms the decoded email token and userId being passed, this is used in the Register method ("/ConfirmEmail" is the endpoint calling this service method)
public async Task<bool> ConfirmToken(string userId, string token) {
var user = await _userManager.FindByIdAsync(userId);
var decodedToken = HttpUtility.UrlDecode(token);
var result = await _userManager.ConfirmEmailAsync(user, decodedToken);
return result.Succeeded;
}