My EmailConfirmed is not working

Nhựt Lại 65 Reputation points
2024-06-20T20:04:22.7933333+00:00

Hi, I'm new to this, so I'm not so good, your help is my pleasure, thank you.
I am having a problem with login, the API emailConfirmed still works when I give it token and userId. As far as I know, the EmailConfirmed doesn't confirm itself. I think I missed an URL in the verification mail, but I don't know how to get a URL for that. If you know any solution or documents, etc.? Please let me know, I would very much appreciate it, please.

using SendGrid.Helpers.Mail;
using System.Net.Mail;
using System.Threading.Tasks;

namespace ChatApp_BE.Helpers
{
    public class IEmailSenders
    {
        private readonly IConfiguration _configuration;

        public IEmailSenders(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public async Task SendEmailAsync(string subject, string toEmail, string message)
        {
            var apiKey = _configuration["SendGrid:ApiSenderKey"];
            var client = new SendGridClient(apiKey);
            var from = new EmailAddress("minhnhut.services.test@gmail.com", "ChapApp");
            var to = new EmailAddress(toEmail);
            //var plainTextContent = message;
            //var htmlContent = message;
            var templateId = _configuration["SendGrid:TemplateId"];
            var templateData = new Dictionary<string, object>
            {
                {"confirm_link", "confirm_link" },
            };
            var msg = MailHelper.CreateSingleTemplateEmail(from, to, templateId, templateData);
            var response = await client.SendEmailAsync(msg);
        }
    }
}

[HttpPost("register")]
public async Task<IActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user != null && await _userManager.IsEmailConfirmedAsync(user))
        {
            return BadRequest("Email was already used.");
        }

        user = new ApplicationUser
        {
            UserName = model.Email,
            Email = model.Email,
            FullName = model.FullName,
        };

        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            user.EmailConfirmationToken = token;
            await _userManager.UpdateAsync(user);
            user.EmailConfirmed = true;

            var confirmationLink = Url.Action(
                nameof(ConfirmEmail),
                "ApplicationUser",
                new { userId = user.Id, token },
                Request.Scheme
                );
            await _emailSender.SendEmailAsync("Confirm your email",
                model.Email,
                $"Please confirm your email by clicking <a href=\"{confirmationLink}\">here</a>.");

            return Ok(new { Message = "Registration successful! Please check your email to confirm your account." });
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    return BadRequest(ModelState);
}

[AllowAnonymous]
[HttpGet("confirmemail")]
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
    var user = await _userManager.FindByIdAsync(userId);
    if (user == null || token == null)
    {
        return BadRequest("Invalid email confirmation request.");
    }

    if (user.EmailConfirmationToken != token)
    {
        return BadRequest("Invalid email confirmation token.");
    }

    var result = await _userManager.ConfirmEmailAsync(user, token);
    //if (user.EmailConfirmationToken == token)
    //{
    //    user.EmailConfirmed = true;
    //}
    if (result.Succeeded)
    {
        user.EmailConfirmationToken = null;
        await _userManager.UpdateAsync(user);
        return Ok("Email confirmed successfully!");
    }
    return BadRequest("Email confirmation failed.");
}

 [HttpPost("login")]
 public async Task<IActionResult> Login(LoginViewModel model)
 {
     try
     {
         if (ModelState.IsValid)
         {
             var user = await _userManager.FindByEmailAsync(model.Email);
             if (!user.EmailConfirmed)
             {
                 return BadRequest("Email is not confirmed.");
             }
             if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password)) 
             {
                 return Unauthorized("Invalid login attempt.");
             }
             var tokenHandler = new JwtSecurityTokenHandler();
             var key = Encoding.UTF8.GetBytes(_config.GetSection("Jwt:SecretKey").Value!);
             var tokenDescriptor = new SecurityTokenDescriptor
             {
                 Subject = new ClaimsIdentity(new Claim[]
                 {
                 new Claim(ClaimTypes.Name, user.UserName),
                 new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
                 }),
                 Expires = DateTime.UtcNow.AddDays(7),
                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
             };
             var token = tokenHandler.CreateToken(tokenDescriptor);
             var tokenString = tokenHandler.WriteToken(token);
             return Ok(new { Token = tokenString });
         }
         return BadRequest(ModelState);
     }
     catch (Exception ex)
     {
         return BadRequest(ex.Message);
     }
 }

Microsoft Identity Manager
Microsoft Identity Manager
A family of Microsoft products that manage a user's digital identity using identity synchronization, certificate management, and user provisioning.
708 questions
Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
754 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,626 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,038 questions
{count} votes

Accepted answer
  1. Ping Ni-MSFT 4,720 Reputation points Microsoft Vendor
    2024-06-21T07:43:14.32+00:00

    Hi @Nhựt Lại,

    Firstly, it seems you cannot get the user.Id in Register action. You need get the id and send to the emailSender like below:

    var userId = await _userManager.GetUserIdAsync(user);
    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
    var confirmationLink = Url.Action(nameof(ConfirmEmail), "ApplicationUser", new { userId = userId, token=code }, Request.Scheme ); 
    await _emailSender.SendEmailAsync("Confirm your email", model.Email, $"Please confirm your email by clicking <a href='{HtmlEncoder.Default.Encode(confirmationLink)}'>here</a>.");
    

    Then in the EmailSender send the link like below:

    var msg = MailHelper.CreateSingleEmail(from, to, subject, message, message);
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Rena

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Pinaki Ghatak 4,685 Reputation points Microsoft Employee
    2024-06-21T10:21:58.48+00:00

    Hello @Nhựt Lại

    You mentioned that the EmailConfirmed property is still true even when you provide it with a token and userId.

    Here are a few things you need to check:

    1. Email Confirmation: In your Register action, you’re setting EmailConfirmed to true right after creating the user. This might be why EmailConfirmed is true even before the email is confirmed. Typically, EmailConfirmed should be set to true only in the ConfirmEmail action after the email confirmation is successful.
    2. User Retrieval: In your Login action, ensure that the user retrieval is working correctly. If the user object is null, it means that the user retrieval failed, and the subsequent check for EmailConfirmed will also fail.
    3. Check the Token: Ensure that the token generated during registration and the one used during email confirmation are the same. You can do this by logging the token value during these two steps and comparing them.
    4. Check the User: Make sure that the user object is being correctly retrieved in both the ConfirmEmail and Login actions. You can do this by logging the user’s email or other unique identifier.
    5. Check the Email Confirmation: In the ConfirmEmail action, ensure that the ConfirmEmailAsync method is successfully confirming the email. You can do this by checking the Succeeded property of the result. If it’s false, log the errors in the result to see what’s going wrong.
    6. Check the Login: In the Login action, make sure that the CheckPasswordAsync method is correctly verifying the password. If it’s returning false, the login will fail. Also, ensure that the EmailConfirmed property of the user is true after the email confirmation.
    7. Error Messages: If there are any error messages or exceptions being thrown, those could provide clues as to what’s going wrong.

    I hope that this response has addressed your query and helped you overcome your challenges. If so, please mark this response as Answered. This will not only acknowledge our efforts, but also assist other community members who may be looking for similar solutions.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.