Be Sociable, Share!

Tweet



Email

WhatsApp



This is the second part of Building Simple Membership system using ASP.NET Identity 2.1, ASP.NET Web API 2.2 and AngularJS. The topics we’ll cover are:

The source code for this tutorial is available on GitHub.

ASP.NET Identity 2.1 Accounts Confirmation, and Password/User Policy Configuration

In this post we’ll complete on top of what we’ve already built, and we’ll cover the below topics:

Send Confirmation Emails after Account Creation.

Configure User (Username, Email) and Password policy.

Enable Changing Password and Deleting Account.

1 . Send Confirmation Emails after Account Creation

ASP.NET Identity 2.1 users table (AspNetUsers) comes by default with a Boolean column named “EmailConfirmed”, this column is used to flag if the email provided by the registered user is valid and belongs to this user in other words that user can access the email provided and he is not impersonating another identity. So our membership system should not allow users without valid email address to log into the system.

The scenario we want to implement that user will register in the system, then a confirmation email will be sent to the email provided upon the registration, this email will include an activation link and a token (code) which is tied to this user only and valid for certain period.

Once the user opens this email and clicks on the activation link, and if the token (code) is valid the field “EmailConfirmed” will be set to “true” and this proves that the email belongs to the registered user.

To do so we need to add a service which is responsible to send emails to users, in my case I’ll use Send Grid which is service provider for sending emails, but you can use any other service provider or your exchange change server to do this. If you want to follow along with this tutorial you can create a free account with Send Grid which provides you with 400 email per day, pretty good!

1.1 Install Send Grid

Now open Package Manager Console and type the below to install Send Grid package, this is not required step if you want to use another email service provider. This packages contains Send Grid APIs which makes sending emails very easy:

install-package Sendgrid 1 install-package Sendgrid

1.2 Add Email Service

Now add new folder named “Services” then add new class named “EmailService” and paste the code below:

public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { await configSendGridasync(message); } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync(IdentityMessage message) { var myMessage = new SendGridMessage(); myMessage.AddTo(message.Destination); myMessage.From = new System.Net.Mail.MailAddress("taiseer@bitoftech.net", "Taiseer Joudeh"); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential(ConfigurationManager.AppSettings["emailService:Account"], ConfigurationManager.AppSettings["emailService:Password"]); // Create a Web transport for sending email. var transportWeb = new Web(credentials); // Send the email. if (transportWeb != null) { await transportWeb.DeliverAsync(myMessage); } else { //Trace.TraceError("Failed to create Web transport."); await Task.FromResult(0); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class EmailService : IIdentityMessageService { public async Task SendAsync ( IdentityMessage message ) { await configSendGridasync ( message ) ; } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync ( IdentityMessage message ) { var myMessage = new SendGridMessage ( ) ; myMessage . AddTo ( message . Destination ) ; myMessage . From = new System . Net . Mail . MailAddress ( "taiseer@bitoftech.net" , "Taiseer Joudeh" ) ; myMessage . Subject = message . Subject ; myMessage . Text = message . Body ; myMessage . Html = message . Body ; var credentials = new NetworkCredential ( ConfigurationManager . AppSettings [ "emailService:Account" ] , ConfigurationManager . AppSettings [ "emailService:Password" ] ) ; // Create a Web transport for sending email. var transportWeb = new Web ( credentials ) ; // Send the email. if ( transportWeb != null ) { await transportWeb . DeliverAsync ( myMessage ) ; } else { //Trace.TraceError("Failed to create Web transport."); await Task . FromResult ( 0 ) ; } } }

What worth noting here that the class “EmailService” implements the interface “IIdentityMessageService”, this interface can be used to configure your service to send emails or SMS messages, all you need to do is to implement your email or SMS Service in method “SendAsync” and your are good to go.

In our case we want to send emails, so I’ve implemented the sending process using Send Grid in method “configSendGridasync”, all you need to do is to replace the sender name and address by yours, as well do not forget to add 2 new keys named “emailService:Account” and “emailService:Password” as AppSettings to store Send Grid credentials.

After we configured the “EmailService”, we need to hock it with our Identity system, and this is very simple step, open file “ApplicationUserManager” and inside method “Create” paste the code below:

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { //Rest of code is removed for clarity appUserManager.EmailService = new AspNetIdentity.WebApi.Services.EmailService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { appUserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity")) { //Code for email confirmation and reset password life time TokenLifespan = TimeSpan.FromHours(6) }; } return appUserManager; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static ApplicationUserManager Create ( IdentityFactoryOptions < ApplicationUserManager > options , IOwinContext context ) { //Rest of code is removed for clarity appUserManager . EmailService = new AspNetIdentity . WebApi . Services . EmailService ( ) ; var dataProtectionProvider = options . DataProtectionProvider ; if ( dataProtectionProvider != null ) { appUserManager . UserTokenProvider = new DataProtectorTokenProvider < ApplicationUser > ( dataProtectionProvider . Create ( "ASP.NET Identity" ) ) { //Code for email confirmation and reset password life time TokenLifespan = TimeSpan . FromHours ( 6 ) } ; } return appUserManager ; }

As you see from the code above, the “appUserManager” instance contains property named “EmailService” which you set it the class we’ve just created “EmailService”.

Note: There is another property named “SmsService” if you would like to use it for sending SMS messages instead of emails.

Notice how we are setting the expiration time for the code (token) send by the email to 6 hours, so if the user tried to open the confirmation email after 6 hours from receiving it, the code will be invalid.

1.3 Send the Email after Account Creation

Now the email service is ready and we can start sending emails after successful account creation, to do so we need to modify the existing code in the method “CreateUser” in controller “AccountsController“, so open file “AccountsController” and paste the code below at the end of the method:

//Rest of code is removed for brevity string code = await this.AppUserManager.GenerateEmailConfirmationTokenAsync(user.Id); var callbackUrl = new Uri(Url.Link("ConfirmEmailRoute", new { userId = user.Id, code = code })); await this.AppUserManager.SendEmailAsync(user.Id,"Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>"); Uri locationHeader = new Uri(Url.Link("GetUserById", new { id = user.Id })); return Created(locationHeader, TheModelFactory.Create(user)); 1 2 3 4 5 6 7 8 9 10 11 //Rest of code is removed for brevity string code = await this . AppUserManager . GenerateEmailConfirmationTokenAsync ( user . Id ) ; var callbackUrl = new Uri ( Url . Link ( "ConfirmEmailRoute" , new { userId = user . Id , code = code } ) ) ; await this . AppUserManager . SendEmailAsync ( user . Id , "Confirm your account" , "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>" ) ; Uri locationHeader = new Uri ( Url . Link ( "GetUserById" , new { id = user . Id } ) ) ; return Created ( locationHeader , TheModelFactory . Create ( user ) ) ;

The implementation is straight forward, what we’ve done here is creating a unique code (token) which is valid for the next 6 hours and tied to this user Id only this happen when calling “GenerateEmailConfirmationTokenAsync” method, then we want to build an activation link to send it in the email body, this link will contain the user Id and the code created.

Eventually this link will be sent to the registered user to the email he used in registration, and the user needs to click on it to activate the account, the route “ConfirmEmailRoute” which maps to this activation link is not implemented yet, we’ll implement it the next step.

Lastly we need to send the email including the link we’ve built by calling the method “SendEmailAsync” where the constructor accepts the user Id, email subject, and email body.

1.4 Add the Confirm Email URL

The activation link which the user will receive will look as the below:

http://localhost/api/account/ConfirmEmail?userid=xxxx&code=xxxx 1 http : / / localhost / api / account / ConfirmEmail ? userid = xxxx & code = xxxx

So we need to build a route in our API which receives this request when the user clicks on the activation link and issue HTTP GET request, to do so we need to implement the below method, so in class “AccountsController” as the new method as the below:

[HttpGet] [Route("ConfirmEmail", Name = "ConfirmEmailRoute")] public async Task<IHttpActionResult> ConfirmEmail(string userId = "", string code = "") { if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(code)) { ModelState.AddModelError("", "User Id and Code are required"); return BadRequest(ModelState); } IdentityResult result = await this.AppUserManager.ConfirmEmailAsync(userId, code); if (result.Succeeded) { return Ok(); } else { return GetErrorResult(result); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [ HttpGet ] [ Route ( "ConfirmEmail" , Name = "ConfirmEmailRoute" ) ] public async Task < IHttpActionResult > ConfirmEmail ( string userId = "" , string code = "" ) { if ( string . IsNullOrWhiteSpace ( userId ) || string . IsNullOrWhiteSpace ( code ) ) { ModelState . AddModelError ( "" , "User Id and Code are required" ) ; return BadRequest ( ModelState ) ; } IdentityResult result = await this . AppUserManager . ConfirmEmailAsync ( userId , code ) ; if ( result . Succeeded ) { return Ok ( ) ; } else { return GetErrorResult ( result ) ; } }

The implementation is simple, we only validate that the user Id and code is not not empty, then we depend on the method “ConfirmEmailAsync” to do the validation for the user Id and the code, so if the user Id is not tied to this code then it will fail, if the code is expired then it will fail too, if all is good this method will update the database field “EmailConfirmed” in table “AspNetUsers” and set it to “True”, and you are done, you have implemented email account activation!

Important Note: It is recommenced to validate the password before confirming the email account, in some cases the user might miss type the email during the registration, so you do not want end sending the confirmation email for someone else and he receives this email and activate the account on your behalf, so better way is to ask for the account password before activating it, if you want to do this you need to change the “ConfirmEmail” method to POST and send the Password along with user Id and code in the request body, you have the idea so you can implement it by yourself 🙂

2. Configure User (Username, Email) and Password policy

2.1 Change User Policy

In some cases you want to enforce certain rules on the username and password when users register into your system, so ASP.NET Identity 2.1 system offers this feature, for example if we want to enforce that our username only allows alphanumeric characters and the email associated with this user is unique then all we need to do is to set those properties in class “ApplicationUserManager”, to do so open file “ApplicationUserManager” and paste the code below inside method “Create”:

//Rest of code is removed for brevity //Configure validation logic for usernames appUserManager.UserValidator = new UserValidator<ApplicationUser>(appUserManager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; 1 2 3 4 5 6 7 //Rest of code is removed for brevity //Configure validation logic for usernames appUserManager . UserValidator = new UserValidator < ApplicationUser > ( appUserManager ) { AllowOnlyAlphanumericUserNames = true , RequireUniqueEmail = true } ;

2.2 Change Password Policy

The same applies for the password policy, for example you can enforce that the password policy must match (minimum 6 characters, requires special character, requires at least one lower case and at least one upper case character), so to implement this policy all we need to do is to set those properties in the same class “ApplicationUserManager” inside method “Create” as the code below:

//Rest of code is removed for brevity //Configure validation logic for passwords appUserManager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = false, RequireLowercase = true, RequireUppercase = true, }; 1 2 3 4 5 6 7 8 9 10 //Rest of code is removed for brevity //Configure validation logic for passwords appUserManager . PasswordValidator = new PasswordValidator { RequiredLength = 6 , RequireNonLetterOrDigit = true , RequireDigit = false , RequireLowercase = true , RequireUppercase = true , } ;

2.3 Implement Custom Policy for User Email and Password

In some scenarios you want to apply your own custom policy for validating email, or password. This can be done easily by creating your own validation classes and hock it to “UserValidator” and “PasswordValidator” properties in class “ApplicationUserManager”.

For example if we want to enforce using only the following domains (“outlook.com”, “hotmail.com”, “gmail.com”, “yahoo.com”) when the user self registers then we need to create a class and derive it from “UserValidator<ApplicationUser>” class, to do so add new folder named “Validators” then add new class named “MyCustomUserValidator” and paste the code below:

public class MyCustomUserValidator : UserValidator<ApplicationUser> { List<string> _allowedEmailDomains = new List<string> { "outlook.com", "hotmail.com", "gmail.com", "yahoo.com" }; public MyCustomUserValidator(ApplicationUserManager appUserManager) : base(appUserManager) { } public override async Task<IdentityResult> ValidateAsync(ApplicationUser user) { IdentityResult result = await base.ValidateAsync(user); var emailDomain = user.Email.Split('@')[1]; if (!_allowedEmailDomains.Contains(emailDomain.ToLower())) { var errors = result.Errors.ToList(); errors.Add(String.Format("Email domain '{0}' is not allowed", emailDomain)); result = new IdentityResult(errors); } return result; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class MyCustomUserValidator : UserValidator < ApplicationUser > { List < string > _allowedEmailDomains = new List < string > { "outlook.com" , "hotmail.com" , "gmail.com" , "yahoo.com" } ; public MyCustomUserValidator ( ApplicationUserManager appUserManager ) : base ( appUserManager ) { } public override async Task < IdentityResult > ValidateAsync ( ApplicationUser user ) { IdentityResult result = await base . ValidateAsync ( user ) ; var emailDomain = user . Email . Split ( '@' ) [ 1 ] ; if ( ! _allowedEmailDomains . Contains ( emailDomain . ToLower ( ) ) ) { var errors = result . Errors . ToList ( ) ; errors . Add ( String . Format ( "Email domain '{0}' is not allowed" , emailDomain ) ) ; result = new IdentityResult ( errors ) ; } return result ; } }

What we have implemented above that the default validation will take place then this custom validation in method “ValidateAsync” will be applied, if there is validation errors it will be added to the existing “Errors” list and returned in the response.

In order to fire this custom validation, we need to open class “ApplicationUserManager” again and hock this custom class to the property “UserValidator” as the code below:

//Rest of code is removed for brevity //Configure validation logic for usernames appUserManager.UserValidator = new MyCustomUserValidator(appUserManager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; 1 2 3 4 5 6 7 //Rest of code is removed for brevity //Configure validation logic for usernames appUserManager . UserValidator = new MyCustomUserValidator ( appUserManager ) { AllowOnlyAlphanumericUserNames = true , RequireUniqueEmail = true } ;

Note: The tutorial code is not using the custom “MyCustomUserValidator” class, it exists in the source code for your reference.

Now the same applies for adding custom password policy, all you need to do is to create class named “MyCustomPasswordValidator” and derive it from class “PasswordValidator”, then you override the method “ValidateAsync” implementation as below, so add new file named “MyCustomPasswordValidator” in folder “Validators” and use the code below:

public class MyCustomPasswordValidator : PasswordValidator { public override async Task<IdentityResult> ValidateAsync(string password) { IdentityResult result = await base.ValidateAsync(password); if (password.Contains("abcdef") || password.Contains("123456")) { var errors = result.Errors.ToList(); errors.Add("Password can not contain sequence of chars"); result = new IdentityResult(errors); } return result; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyCustomPasswordValidator : PasswordValidator { public override async Task < IdentityResult > ValidateAsync ( string password ) { IdentityResult result = await base . ValidateAsync ( password ) ; if ( password . Contains ( "abcdef" ) || password . Contains ( "123456" ) ) { var errors = result . Errors . ToList ( ) ; errors . Add ( "Password can not contain sequence of chars" ) ; result = new IdentityResult ( errors ) ; } return result ; } }

In this implementation we added some basic rule which checks if the password contains sequence of characters and reject this type of password by adding this validation result to the Errors list, it is exactly the same as the custom users policy.

Now to attach this class as the default password validator, all you need to do is to open class “ApplicationUserManager” and use the code below:

//Rest of code is removed for brevity // Configure validation logic for passwords appUserManager.PasswordValidator = new MyCustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = false, RequireLowercase = true, RequireUppercase = true, }; 1 2 3 4 5 6 7 8 9 10 //Rest of code is removed for brevity // Configure validation logic for passwords appUserManager . PasswordValidator = new MyCustomPasswordValidator { RequiredLength = 6 , RequireNonLetterOrDigit = true , RequireDigit = false , RequireLowercase = true , RequireUppercase = true , } ;

All other validation rules will take place (i.e checking minimum password length, checking for special characters) then it will apply the implementation in our “MyCustomPasswordValidator”.

3. Enable Changing Password and Deleting Account

Now we need to add other endpoints which allow the user to change the password, and allow a user in “Admin” role to delete other users account, but those end points should be accessed only if the user is authenticated, we need to know the identity of the user doing this action and in which role(s) the user belongs to. Until now all our endpoints are called anonymously, so lets add those endpoints and we’ll cover the authentication and authorization part next.

3.1 Add Change Password Endpoint

This is easy to implement, all you need to do is to open controller “AccountsController” and paste the code below:

[Route("ChangePassword")] public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model) { if (!ModelState.IsValid) { return BadRequest(ModelState); } IdentityResult result = await this.AppUserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword); if (!result.Succeeded) { return GetErrorResult(result); } return Ok(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ Route ( "ChangePassword" ) ] public async Task < IHttpActionResult > ChangePassword ( ChangePasswordBindingModel model ) { if ( ! ModelState . IsValid ) { return BadRequest ( ModelState ) ; } IdentityResult result = await this . AppUserManager . ChangePasswordAsync ( User . Identity . GetUserId ( ) , model . OldPassword , model . NewPassword ) ; if ( ! result . Succeeded ) { return GetErrorResult ( result ) ; } return Ok ( ) ; }

Notice how we are calling the method “ChangePasswordAsync” and passing the authenticated User Id, old password and new password. If you tried to call this endpoint, the extension method “GetUserId” will not work because you are calling it as anonymous user and the system doesn’t know your identity, so hold on the testing until we implement authentication part.

The method “ChangePasswordAsync” will take care of validating your current password, as well validating your new password policy, and then updating your old password with new one.

Do not forget to add the “ChangePasswordBindingModel” to the class “AccountBindingModels” as the code below:

public class ChangePasswordBindingModel { [Required] [DataType(DataType.Password)] [Display(Name = "Current password")] public string OldPassword { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "New password")] public string NewPassword { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "Confirm new password")] [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ChangePasswordBindingModel { [ Required ] [ DataType ( DataType . Password ) ] [ Display ( Name = "Current password" ) ] public string OldPassword { get ; set ; } [ Required ] [ StringLength ( 100 , ErrorMessage = "The {0} must be at least {2} characters long." , MinimumLength = 6 ) ] [ DataType ( DataType . Password ) ] [ Display ( Name = "New password" ) ] public string NewPassword { get ; set ; } [ Required ] [ DataType ( DataType . Password ) ] [ Display ( Name = "Confirm new password" ) ] [ Compare ( "NewPassword" , ErrorMessage = "The new password and confirmation password do not match." ) ] public string ConfirmPassword { get ; set ; } }

3.2 Delete User Account

We want to add the feature which allows a user in “Admin” role to delete user account, until now we didn’t introduce Roles management or authorization, so we’ll add this end point now and later we’ll do slight modification on it, for now any anonymous user can invoke it and delete any user by passing the user Id.

To implement this we need add new method named “DeleteUser” to the “AccountsController” as the code below:

[Route("user/{id:guid}")] public async Task<IHttpActionResult> DeleteUser(string id) { //Only SuperAdmin or Admin can delete users (Later when implement roles) var appUser = await this.AppUserManager.FindByIdAsync(id); if (appUser != null) { IdentityResult result = await this.AppUserManager.DeleteAsync(appUser); if (!result.Succeeded) { return GetErrorResult(result); } return Ok(); } return NotFound(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [ Route ( "user/{id:guid}" ) ] public async Task < IHttpActionResult > DeleteUser ( string id ) { //Only SuperAdmin or Admin can delete users (Later when implement roles) var appUser = await this . AppUserManager . FindByIdAsync ( id ) ; if ( appUser != null ) { IdentityResult result = await this . AppUserManager . DeleteAsync ( appUser ) ; if ( ! result . Succeeded ) { return GetErrorResult ( result ) ; } return Ok ( ) ; } return NotFound ( ) ; }

This method will check the existence of the user id and based on this it will delete the user. To test this method we need to issue HTTP DELETE request to the end point “api/accounts/user/{id}”.

The source code for this tutorial is available on GitHub.

In the next post we’ll see how we’ll implement Json Web Token (JWTs) Authentication and manage access for all the methods we added until now.

Follow me on Twitter @tjoudeh

References