skip to Main Content

I am making api in .net 5 and implementing sending email confirmation after succesful registration. This one is killing me and can’t see the reason why it returns Invalid Token.

So here’s my code:

Startup.cs

services.AddAuthentication
...
...
...
services.AddIdentityCore<User>(config =>
{
  config.SignIn.RequireConfirmedEmail = true;

}).AddEntityFrameworkStores<WorkoutDbContext>()
.AddDefaultTokenProviders();

EmailHelper.cs

    public class EmailHelper
    {
        public bool SendEmail(string userEmail, string confirmationLink)
        {
            MailMessage mailMessage = new MailMessage();
            mailMessage.From = new MailAddress("noreply@*********.io");
            mailMessage.To.Add(new MailAddress(userEmail));

            mailMessage.Subject = "Confirm your email";
            mailMessage.IsBodyHtml = false;
            mailMessage.Body = confirmationLink;
            SmtpClient client = new SmtpClient();
            client.UseDefaultCredentials = false;
            client.Credentials = new System.Net.NetworkCredential("noreply@*********.io", "super secret password");
            client.Host = "host";
            client.Port = 000;
            client.EnableSsl = false;
            client.DeliveryFormat = (SmtpDeliveryFormat)SmtpDeliveryMethod.Network;
            try
            {
                client.Send(mailMessage);
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                // log exception
            }
            return false;
        }
    }

AccountService.cs

        public async void RegisterUser(RegisterUserDto dto)
        {
            var uniqueId = Guid.NewGuid().ToString()[..8];
            var newUser = new User()
            {
                Name = dto.Name,
                LastName = dto.LastName,
                Email = dto.Email,
                EmailConfirmed = false,
                UniqeId = uniqueId,
                RegistrationDate = DateTime.Now,
                Active = false,
                RoleId = dto.RoleId
            };

            var hashedPassword = _passwordHasher.HashPassword(newUser, dto.Password);
            newUser.Password = hashedPassword;
            newUser.NormalizedEmail = dto.Email;
            _context.Add(newUser);
            _context.SaveChanges();
            var user = await _userManager.FindByEmailAsync(newUser.Email);
            if(user != null)
            {
                EmailHelper emailHelper = new EmailHelper();
                var token = HttpUtility.UrlEncode(await _userManager.GenerateEmailConfirmationTokenAsync(newUser));
                var confirmationLink = "http://localhost:5000/confirmEmail?token=" + token + "&email=" + newUser.NormalizedEmail;
                bool emailResponse = emailHelper.SendEmail(newUser.NormalizedEmail, confirmationLink);
            } 

        }

User registration works fine. If email is not registered than registration fails. Otherwise server return 200 as expected. Confirmation link is sent to email provided during registration process. So till now works as expected.

EmailController.cs

        [HttpGet]
        public async Task<IdentityResult> ConfirmEmail([FromQuery]string token, [FromQuery] string email)
        {
            var user = await _userManager.FindByEmailAsync(email);
            var result = await _userManager.ConfirmEmailAsync(user, token);
            if (result.Succeeded) { return IdentityResult.Success; }
            else { return (IdentityResult) result;  }

        }

Problem start when clicking on activation link – it throws warn: Microsoft.AspNetCore.Identity.UserManager[9] VerifyUserTokenAsync() "code": "InvalidToken",
So activation link is sent, it checks if user !== null and than goes to conditional statement and triggers EmailHelper which sends link to inbox correctly.

…BUT

When I try to console user – it’s empty, nothing shows in console.
Database is updated and user is saved in DB and it’s visible and accessible.
In EmailController.cs it does not return user

   var user = await _userManager.FindByEmailAsync(email);
   // if I try to console it it shows nothing - empty(not NULL)

If I try to send serialized user data to inbox instead of sending activation link it sends correct data from DB.

So to sum up:

  • user is created and saved in DB,
  • sending user data to inbox instead of confirmationLink sends accurate and correct data of user from database and proves that everything went ok
  • confirmation link is generated correctly and send to inbox
  • checking var user = await _userManager.FindByEmailAsync(newUser.Email); if(user != null) makes user NOT null send allows to send email

And this is killing me and do not know why checking if user exists gives true but trying to access via await _userManager.FindByEmailAsync(newUser.Email); gives no user(nothing)?
I tried also (in EmailController) _context.Users.FirstOrDefault(u => u.NormalizedEmail == email) but the result is the same – cannot get user from DB and returns

Microsoft.AspNetCore.Identity.UserManager[9]
      VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user.

UPDATE
IdentityResult output

{
  "succeeded": false,
  "errors": [
    {
      "code": "InvalidToken",
      "description": "Invalid token."
    }
  ]
}

UPDATE #2

When I serialize user and try to display in console it actually shows correct user data as a string. The problem seems to be that neither
var user = await _userManager.FindByEmailAsync(email);
nor var user= _context.Users.FirstOrDefault(u => u.Email == email); does not return user that can be verified.

UPDATE #3

No IUserTwoFactorTokenProvider<TUser> named 'xxxx' is registered. System.NotSupportedException: No IUserTwoFactorTokenProvider<TUser> named 'xxxx' is registered.
But AddDefaultTokenProviders() was implemented in Startup.cs
Even tried config.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
or
config.Tokens.ProviderMap.Add("Default", new TokenProviderDescriptor(typeof(IUserTwoFactorTokenProvider<User>))); but the result is the same.

2

Answers


  1. Chosen as BEST ANSWER

    I was working on the solution for past few days but finally found a solution!

    Since I am using IdentityUser but my User model did not contain table columns from Identity it cause problem, most likely because that security stamp column was not present.

    What I did was to include table columns from IdentityUser.

    I have also included

                services.AddIdentityCore<User>(config =>
                {
                    config.SignIn.RequireConfirmedEmail = true;
                    config.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; <--- this line!!
                }).AddEntityFrameworkStores<WorkoutDbContext>()
                .AddDefaultTokenProviders();
    

    Also in EmailController.cs in ConfirmEmail I had to decode token because I was encoding this with HttpUtility.Encode when egenrating. var tokenDecode = HttpUtility.UrlDecode(token);

    Than I checked with VerifyUserTokenAsync which returned true. After that I only received confirmation error when email was clicked that Username '' is invalid. Username can have only letters and number.

    What I also did was generating userName containing only letters and numbers from email that was used for registration and assigning it to UserName column in DB set.

    After that when email was sent and clicked it returned IdentityResult.Success

    Damn! It was a long way home. :)


  2. In my .NET core 6, encode send via email:

    var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
    token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));
    

    Then you should decode token before confirm:

    var token  = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Token));
    var result = await _userManager.ConfirmEmailAsync(user, token);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search