Welcome to the new blog. This is the continuation to Authentication using ASP.NET Core Identity. In the previous blog we learnt how to implement register and login for our ASP.net Core Web API project using ASP.NET Core Identity.
From now user can register and login themself using their email id. But how we'll verify that they have shared the valid email. From the input validation we can only validate the email format is correct or not but the email is correct or valid, we can't verify with that.
ASP.NET Core Identity provides below APIs to verify/confirm an email.
- GET /confirmEmail
- POST /resendConfirmationEmail
And the following APIs to reset password
- POST /forgotPassword
- POST /resetPassword
By default, all the user that are registered can login without verifying their email but we can allow only the verified user can only able to login. Below option need to be set on program.cs
builder.Services.Configure<IdentityOptions>(options =>
{
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(10);
});
So, when only the signin.RequireConfirmEmail option is true, it will only allow the user to login which email id is verified. When the above option is true, it automatically send an email with confirmation link to verify the email. However, we can also call the POST /resendConfirmationEmail to resend the verification link to that email id if the link is expired or missed due to other reason.
In the confirmation email it by default sends the link to GET /confirmEmail with all the needed parameter. However, we can customize this as per our requirement.
But, how ASP.NET Core Identity will know from which sender the email will be sent. The answer is we've to configure. Let's configure the same.
Create an email provider
For this blog we're using Sendgrid as an email provider. However we can use any Email Provider of our choice. We can also use SMTP to send emails but Microsoft recommends to use an email service provider for security.
So, we'll create a Sendgrid account and add a Sender. After adding a sender we'll get a KEY for that.
Add the key to secret
We'll add the above sendgrid key to User Secret for secure storage.
We'll also create a class to fetch the SendgridEmailKey from UserSecret.
namespace EmployeeManagement.Services.Common
{
public class MessageSenderOptions
{
public required string SendgridApiKey { get; set; }
}
}
Register this class to our service container like this.
builder.Services.Configure<MessageSenderOptions>(builder.Configuration);
This will bind the value from application configuration matching with property name to our MessagSenderOption class and we can inject this class to anywhere in our app to get the value directly without injecting the configuration.
Add an EmailSenderService class
We'll create an interface named as IEmailSenderService and EmailSendeService class under a new folder named Services/Common.
IEmailSenderService will handle Sending email for our entire application (not only for identity but for every use case where we want to send email). Here is the declaration and implementation of the Email Service class.
namespace EmployeeManagement.Services.Common
{
public interface IEmailSenderService
{
Task SendEmailAsync(string email, string subject, string message);
}
}
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace EmployeeManagement.Services.Common
{
public class EmailSenderService(IOptions<MessageSenderOptions> options) : IEmailSenderService
{
private readonly MessageSenderOptions messageSenderOptions = options.Value;
public async Task SendEmailAsync(string email, string subject, string message)
{
var client = new SendGridClient(messageSenderOptions.SendgridApiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("<--Sender email id ", "LetsFixIt"),
Subject = subject,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
msg.SetClickTracking(false, false);
_ = await client.SendEmailAsync(msg);
}
}
}
Register this as a singleton service to our container.
builder.Services.AddSingleton<IEmailSenderService, EmailSenderService>();
Implement IEmailSender<T> from Identity
Now, we'll create a class named IdentityEmailService which implements from IEmailSender<T>. Here T is IdentityUser class, which we used to configure Identity.
IEmailSender<T> is provided by ASP.NET Identity. ASP.NET Identity will call IEmailSender<T> when confirmEmail or forgotPassword APIs are executed with required information.
using EmployeeManagement.Services.Common;
using Microsoft.AspNetCore.Identity;
namespace EmployeeManagement.Services
{
public class IdentityEmailService(IEmailSenderService emailSenderService) : IEmailSender<IdentityUser>
{
private readonly IEmailSenderService _emailSenderService = emailSenderService;
public async Task SendConfirmationLinkAsync(IdentityUser user, string email, string confirmationLink)
{
string confimationLinkMessageHtml = $"<b>Please <a href='" + confirmationLink + "'>click here</a> to confirm your email.</b>";
await _emailSenderService.SendEmailAsync(email, "Confirm Your Email!", confimationLinkMessageHtml);
}
public async Task SendPasswordResetCodeAsync(IdentityUser user, string email, string resetCode)
{
string resetCodeMessageHtml = $"<p>Here is the reset code for changing your password. Code : <b>"+resetCode+"</b></p>";
await _emailSenderService.SendEmailAsync(email, "Reset Your Password!", resetCodeMessageHtml);
}
public Task SendPasswordResetLinkAsync(IdentityUser user, string email, string resetLink)
{
throw new NotImplementedException();
}
}
}
Here, Identity will call the SendConfirmationLinkAsync(IdentityUser user, string email, string confirmationLink) method when POST /resendConfirmationEmail is executed or User is registered. Rest our IEmailSenderService will handle sending the email to given link.
Similarly, it will call SendPasswordResetCodeAsync(IdentityUser user, string email, string resetCode) when POST /resetPassword is executed. Rest will be handled by IEmailSenderService.
Note: The SendPasswordResetLinkAsync(IdentityUser user, string email, string resetLink) is not called from any IdentityAPIs. It will used by server side rendering applications.
Let's register this dependency to service container.
builder.Services.AddTransient<IEmailSender<IdentityUser>, IdentityEmailService>();
Now, Let's test our implementation
Test Email Confirmation
As, we've enabled requireConfirmedEmail = true in options above. So, the email must be confirmed to login to be able to login.
So, let's start confirming the email. We'll execute POST /resendConfirmationEmail to send the confirmation link to the email address user has registered themself. When user register himself, it will automatically send the confirmation link using the above implementation. But, if we want to send the link again then we can use this API.
After clicking the link sent over email. We'll get a successful message of email confirmation. The email confirmation link is built with GET /confirmEmail. We can use use the defult link or we can customize as per the requirement.
Test ResetPassword
If somehow user forgot his password, we can reset the password following the steps.
First we've to pass the emailId to POST /forgotPassword API. It will send an email to that emailId containing the reset code. That resetCode we've to pass into the POST /resetPassword along with other info present in request body. After that user can login with the new password. Here is the sample request body for POST /resetPassword API.
{
"email": "string",
"resetCode": "string",
"newPassword": "string"
}
DefaultLockOut
In the beginning we've added some IdentityOptions. First two are discussed above already but we've not discussed about options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(300);
This option is used to lock the user after providing continuous wrong password for login. The default is continuous 5 attempts. After the user account is lock he won't be able to login. After 300 sec user can try again. However we can change the no of lockout attempts as per our requirement.Also, we can customize the timespan as well.
Conclusion
In this blog, we learnt how to allow only confirmed user to be able to login to our Web APIs as well as how to reset password if user wants to change the password or somehow forgot. The above feature is used in almost all the web application and also considered as standard for Authentication. However, we can customize the email message and flow as per the requirement, but behind the scene we've to use these APIs to confirm or reset password.
I hpoe you like this. Please like and share your feedback with us.
Thank You!

Comments
Post a Comment