Continued from: ASP.NET MVC 2 Custom Membership Provider Tutorial – Part 2

In the previous part of the tutorial we implemented CreateUser method which successfully creates new user in the database.

First thing we want to do now is to generate password salt.

Open UserRepository.cs, and add a reference to System.Security.Cryptography:

using System.Security.Cryptography;

and function to generate the salt (within UserRepository class):

private static string CreateSalt()
{
  RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  byte[] buff = new byte[32];
  rng.GetBytes(buff);

  return Convert.ToBase64String(buff);
}

Finally,change the CreateUser method to generate and save the salt to database with other user data.

public MembershipUser CreateUser(string username, string password, string email)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  User user = new User();

  user.UserName = username;
  user.Email = email;
  user.Password = password;
  user.PasswordSalt = CreateSalt();
  user.CreatedDate = DateTime.Now;
  user.IsActivated = false;
  user.IsLockedOut = false;
  user.LastLockedOutDate = DateTime.Now;
  user.LastLoginDate = DateTime.Now;

  db.AddToUsers(user);
  db.SaveChanges();

  return GetUser(username);
 }
}

We can go ahead and try it. Run your application and register new user account. If you check the database you will see random salt generated during the registration.

Now, let’s hash the password. Add the following code to UserRepository class:

private static string CreatePasswordHash(string pwd, string salt)
{
 string saltAndPwd = String.Concat(pwd, salt);
 string hashedPwd =
         FormsAuthentication.HashPasswordForStoringInConfigFile(
         saltAndPwd, "sha1");
 return hashedPwd;
}

and modify CreateUser method again to store hashed password. Because we’re going to use user.passwordSalt property as an argument in CreatePasswordHash method we need to reorder the properties so the salt gets generated before the password is hashed:

public MembershipUser CreateUser(string username, string password, string email)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
   User user = new User();

   user.UserName = username;
   user.Email = email;
   user.PasswordSalt = CreateSalt();
   user.Password = CreatePasswordHash(password, user.PasswordSalt);
   user.CreatedDate = DateTime.Now;
   user.IsActivated = false;
   user.IsLockedOut = false;
   user.LastLockedOutDate = DateTime.Now;
   user.LastLoginDate = DateTime.Now;

   db.AddToUsers(user);
   db.SaveChanges();

   return GetUser(username);
 }
}

I am aware that this is probably not the best method to hash the password and that the hashing and salt generating methods can be improved but this is not the point of this tutorial so I’ll leave it as it is.

The UserRepository.cs file up to this point in the tutorial can be downloaded here:

UserRepository.cs

We now have hashed password and salt stored in the database so let’s implement ValidateUser method properly.

Create ValidateUser method in UserRepository class:

public bool ValidateUser(string username, string password)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  var result = from u in db.Users where (u.UserName == username) select u;

  if (result.Count() != 0)
  {
   var dbuser = result.First();

   if (dbuser.Password == CreatePasswordHash(password, dbuser.PasswordSalt))
    return true;
   else
    return false;
  }
  else
  {
   return false;
  }
 }
}

and modify ValidateUser method in MyMembershipProvider class:

public override bool ValidateUser(string username, string password)
{
 UserRepository _user = new UserRepository();

 return _user.ValidateUser(username, password);
}

At this point we can run the application, register new account, log off and log in with it.

By the way, let’s tweak our application so it doesn’t log newly registered users in after the registration automatically.

We will want our users to activate the account by clicking the link in an email. Until activated user will not be able to log in (we will implement that in a second too).

Let’s create a View that will be displayed to users after the registration.

Open HomeController.cs file, and add Welcome method:

public ActionResult Welcome()
{
 return View();
}

Right click on Welcome() and Add View…

In the View, add a message to newly registered users:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 <h2>Welcome</h2>

 <p>Thank you for registering!</p>

 <p>Activation email has been sent to you. Click on the link in
 the email to activate your account!</p>

</asp:Content>

Now, open AccountController.cs file, locate Register method and change:

if (createStatus == MembershipCreateStatus.Success)
{
 FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
 return RedirectToAction("Index", "Home");
}

so it looks like this:

if (createStatus == MembershipCreateStatus.Success)
{
 return RedirectToAction("Welcome", "Home");
}

If we run our application now and register new account we will not end up being logged in and we will be redirected to the Welcome page. Great!

Let’s generate email activation key and store it with the user data in the database.

Add the following method to UserRepository class:

private static string GenerateKey()
{
 Guid emailKey = Guid.NewGuid();

 return emailKey.ToString();
}

and modify the CreateUser method so the key is saved to the database (In the NewEmailKey column):

public MembershipUser CreateUser(string username, string password, string email)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  User user = new User();

  user.UserName = username;
  user.Email = email;
  user.PasswordSalt = CreateSalt();
  user.Password = CreatePasswordHash(password, user.PasswordSalt);
  user.CreatedDate = DateTime.Now;
  user.IsActivated = false;
  user.IsLockedOut = false;
  user.LastLockedOutDate = DateTime.Now;
  user.LastLoginDate = DateTime.Now;
  user.NewEmailKey = GenerateKey();

  db.AddToUsers(user);
  db.SaveChanges();

  return GetUser(username);
 }
}

Now, the activation key is generated we will email the activation link to the user.

For the purpose of this tutorial I will implement the email code directly into UserRepository class. In real world scenario you would ideally want to keep the email class separate. You would also want to add some sort of error handling for the email class.

You will also need an SMTP server that allows relaying messages. When you run the application on the Web Server from an ISP, usually it has SMTP role installed and you can use localhost as a server. This isn’t the case with Web Server installed with Visual Web Developer so you will need to find a server which will allow the application to send emails.

First, add a reference to System.Net.Mail namespace (UserRepository.cs):

using System.Net.Mail;

In CreateUser method, add the following code:

public MembershipUser CreateUser(string username, string password, string email)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  User user = new User();

  user.UserName = username;
  user.Email = email;
  user.PasswordSalt = CreateSalt();
  user.Password = CreatePasswordHash(password, user.PasswordSalt);
  user.CreatedDate = DateTime.Now;
  user.IsActivated = false;
  user.IsLockedOut = false;
  user.LastLockedOutDate = DateTime.Now;
  user.LastLoginDate = DateTime.Now;
  user.NewEmailKey = GenerateKey();

  db.AddToUsers(user);
  db.SaveChanges(); 

  string ActivationLink = "http://localhost:PORT/Account/Activate/" +
                                      user.UserName + "/" + user.NewEmailKey;

  var message = new MailMessage("EMAIL_FROM", user.Email)
  {
   Subject = "Activate your account",
   Body = ActivationLink
  };

  var client = new SmtpClient("SERVER");
  client.Credentials = new System.Net.NetworkCredential("USERNAME", "PASSWORD");
  client.UseDefaultCredentials = false;

  client.Send(message);

  return GetUser(username);
 }
}

You will need to replace PORT (with your local application port name or remove if on live server), EMAIL_FROM, SERVER, USERNAME and PASSWORD values.

If during registration process the application throws an exception like this:

That means that the server doesn’t allow relaying and you need to find another server (not easy I know).

Once you’ll get past this step (manually removing all created up to this point accounts from the database), you will receive an email with the link.

So let’s add the activation functionality to the AccountController.

Open AccountController.cs and add the following code:

// **************************************
// URL: /Account/Activate/username/key
// **************************************

public ActionResult Activate(string username, string key)
{
 UserRepository _user = new UserRepository();
 if (_user.ActivateUser(username, key) == false)
  return RedirectToAction("Index", "Home");
 else
  return RedirectToAction("LogOn");
}

For the activation Url to work we will need to edit Global.asax.cs file and add the following route:

routes.MapRoute(
 "Activate",
 "Account/Activate/{username}/{key}",
 new { controller = "Account", action = "Activate",
 username = UrlParameter.Optional, key = UrlParameter.Optional } );

And finally, ActivateUser method in UserRepository class:

public bool ActivateUser(string username, string key)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  var result = from u in db.Users where (u.UserName == username) select u;

  if (result.Count() != 0)
  {
   var dbuser = result.First();

   if (dbuser.NewEmailKey == key)
   {
    dbuser.IsActivated = true;
    dbuser.LastModifiedDate = DateTime.Now;
    dbuser.NewEmailKey = null;

    db.SaveChanges();

    return true;
   }
   else
   {
    return false;
   }

  }
  else
  {
   return false;
  }
 }
}

Now, let’s modify ValidateUser method so it will only authenticate activated users:

public bool ValidateUser(string username, string password)
{
 using (CustomMembershipDB db = new CustomMembershipDB())
 {
  var result = from u in db.Users where (u.UserName == username) select u;

  if (result.Count() != 0)
  {
   var dbuser = result.First();

   if (dbuser.Password == CreatePasswordHash(password, dbuser.PasswordSalt) &&
                                                       dbuser.IsActivated == true)
    return true;
   else
    return false;
  }
  else
  {
   return false;
  }
 }
}

Run the application and register 2 user accounts. Validate one of them using the link from the email. You will be able to log in only using the activated account.

In the next part of the tutorial we will add some error handling as well as some Views to support implemented processes.

Sorry folks! Due to new role I have taken, I won’t be able to continue this tutorial any time soon.

Files at the end of this part of the tutorial:

Global.asax.cs

AccountController.cs

UserRepository.cs


Tags: , ,

41 Responses


  1. liam on 14 Dec 2010

    Great series so far. Thank you very much. Will this series have anything about implementing a custom profile provider in it?

    • LukeP on 14 Dec 2010

      Thanks. Ideally I would like to cover custom implementation of both Role and Profile providers. It would make sense to base it on what we’ve done so far and make it all-in-one tutorial.

      Unfortunately at this stage I can’t really tell when the Profile Provider will be covered. We’re getting closer to Role Provider though.

      • Mihai on 01 Feb 2011

        When the role provider tutorial will be ready?
        this is a really cod tutorial made me understand how .NET Membership works

  2. Liam on 14 Dec 2010

    It’s all good mate. .NET Membership has thoroughly confused me so far, but this series is helping a lot. Keep up the good work!

    • LukeP on 14 Dec 2010

      Glad to hear it’s helpful. Thanks

      By the way, did you get the email verification working? I was wondering if I should include that due to hardly any servers allowing relaying and probably plenty of people following the tutorial on their home machines. In the end I thought it would be useful and you can always activate the account directly in the database (or create activated account)

  3. Liam on 14 Dec 2010

    I haven’t tried it yet.

    Only part i did have trouble with was that .NET wouldn’t find the myMembershipProvider in the App_Data folder with the settings that you had. In the end i moved it back into the models folder and fully qualified it in web.config ie: CustomMembership.Models.MyMembershipProvider and then it worked fine.

  4. Andy on 05 Jan 2011

    I got the email verification working with no real issues (but I have an account setup for the purpose. It shouldn’t be an issue for most email systems anyway as long as they allow authentication).

    A note to others implementing this (from experience) – don’t use Google as your email provider. Although you will get it working in test, when you move to production any volume of identical emails will get blocked.

    I’d also like to see you move onto the Role provider as that’s where I’m scratching my head today.

    Thanks for the great work.

    • LukeP on 05 Jan 2011

      Thanks.

      Will try to move on with the next part soon. Been really busy lately but things are starting to settle down a bit.

  5. menthol on 09 Jan 2011

    Thanks! great tutorial series.

    I have read many other tutorials about custom membership providers and have to say that yours is by far the best and easiest to follow!

  6. Ghandar on 10 Jan 2011

    Awesome tutorials. I have read many of them but didn’t see any interesting. Hope you’ll continue this and… tell us about more things we don’t know yet. Personally I’d read about changing password, editing in repository, complete ajax validation (f.e. user exists in database) and much more.

    As a newbie I’ve to say: it’s really easy with you.
    See you soon!

    Greetings,
    Martin

  7. grml on 11 Jan 2011

    Will you explain how pagination works?

  8. fcrist on 12 Jan 2011

    In the Activate method (Global.asax.cs) snippet you have:
    new { controller = “Account”, action = “LogOn”,…

    It should be:
    new { controller = “Account”, action = “Activate”,…

    otherwise the activation does not actually happen.

    The Global.asax.cs is fine though.
    Thanks! When do you think the next part will be ready?

    • LukeP on 12 Jan 2011

      Hey, thanks for pointing that out. I’ve corrected the error.

      I’ve been really busy lately with work and other project of mine but I’m planning to post it next weekend or the weekend after. Apologies for the delays :D

  9. fcrist on 12 Jan 2011

    Can you please show how the code needs to be changed to collect from the user other fields, such as address, tel. number, etc.?

    • Ghandar on 12 Jan 2011

      Send me your email, I’ll try say what you need to do.

      • fcrist on 12 Jan 2011

        fcrist at yahoo.com

        Thanks!

  10. Dan on 17 Jan 2011

    Hi!
    I just would to congratulate you for this great tutorial. If anyone is using the lastest release of VS2010, no tutorial on the internet will fit but this. This tutorial is the only one that had worked 100% fine.

    Thank you!

    Dan Perciani – Brazil

    • Dan on 17 Jan 2011

      I’d be very thankful if someone could explain to me how to merge this Custom Membership Provider with Authentication and Authorization, allowing users to access the files only after loggin in.
      I’m very noobie to MVC and I had being tryin , but I dont know how to associate it to this stuff.

      Once again, thanks very much.

      • Mihai on 02 Feb 2011

        you can use the attribute
        [Authorize]
        for entire controller class or for each action in part…
        something like this:
        [Authorize]
        public class OperatorController : Controller
        {

        }

        or
        [Authorize]
        public ActionResult Index()
        {

        }
        something like this…

  11. Trevor on 28 Jan 2011

    When do you think you’ll be able to post the remaining article(s)?

  12. moble on 28 Jan 2011

    Great Stuff.. Really Helped ..
    Please do post the custom profile tutorial also ..
    It’s really missing to this one

  13. awj on 29 Jan 2011

    I was having trouble with the ASPNET membership provider because I couldn’t get it installed on my web host’s server, and then I came across this – absolutely ideal.

    Thoroughly explained and easy to follow (so I can see how to customise it further should I need to), easily implementable on my host’s server, and slots in perfectly with my MVC/EF app.

    Roll on part 4.

  14. Robin on 04 Feb 2011

    Great tutorial. I’d implemented a custom MembershipProvider and thought I’d got my head around it but this showed me the right way to do it. Like everyone else here I’m eager for the next part.

  15. Nivash on 18 Feb 2011

    dude! this article rocks!
    please keep posted for the roles and profile tutorials and please keep it simple like this one. This tutorial is clearly explained and I would like to say thank you!

    Please keep me updated for the other mvc tutorials.

    Thanks

  16. x51r on 21 Feb 2011

    I have error
    The password retrieval answer provided is invalid. Please check the value and try again.

  17. Matty on 14 Mar 2011

    Excellent tutorials, easy to understand and follow

    Very helpful to me- im currently doing my final year project at university.. I modified it to work with my wcf service to connect to the database using entity framework there and had no issues at all..

    I would also love to see your next section on roles (hopefuly before my handin date!)

    Thanks again

  18. Chizzle on 15 Mar 2011

    Best tutorial I’ve seen so far, so I really hope you continue with the custom role and profile provider implementation…

    Any chance you will do that?

  19. Heath on 31 Mar 2011

    This really was both extremely informative and easy to follow. I’m new to MVC, and after reading your articles in this series things are certainly much clearer. Thanks for the effort and time you’ve put into this and for making it freely available to others. Great work!

  20. Sweet_Simran on 27 Apr 2011

    Hello Friends,
    I also working on the custom membership using Entity framework.
    It has almost 40 coloums.
    I want to store that using cutom membership and also i want to create the ragistration page on MVC asp.net depends on these coloums.
    Please help me to create UI in ASp.net MVC and as well the custom membership class to handle these coloums.

    Please See the scripts to get idea about the database.

    // CREATE TABLE [dbo].[Users](
    // [UserID] [int] IDENTITY(1,1) NOT NULL,
    // [Username] [varchar](50) NULL,
    // [Password] [varchar](50) NULL,
    // [Firstname] [varchar](50) NULL,
    // [Middle] [varchar](50) NULL,
    // [Lastname] [varchar](50) NULL,
    // [EmailAddress] [varchar](50) NULL,
    // [Company] [varchar](100) NULL,
    // [Address1] [varchar](100) NULL,
    // [Address2] [varchar](100) NULL,
    // [City] [varchar](50) NULL,
    // [State] [varchar](50) NULL,
    // [ZipCode] [varchar](10) NULL,
    // [Country] [varchar](50) NULL,
    // [PhoneHome] [varchar](50) NULL,
    // [PhoneWork] [varchar](50) NULL,
    // [PhoneFax] [varchar](50) NULL,
    // [LastLoginDate] [datetime] NULL,
    // [CreateDate] [datetime] NULL,
    // [IsJobSeeker] [bit] NOT NULL,
    // [IsEmployer] [bit] NOT NULL,
    // [LogoFilename] [varchar](50) NULL,
    // [SalespersonID] [int] NULL,
    // [Marketing] [bit] NOT NULL,
    // [ReferredBy] [varchar](500) NULL,
    // [DoNotSend1] [varchar](50) NULL,
    // [DoNotSend2] [varchar](50) NULL,
    // [DoNotSend3] [varchar](50) NULL,
    // [RegisterFrom] [varchar](50) NULL,
    // [Recognized] [bit] NULL,
    // [Relocate] [varchar](50) NULL,
    // [SendToEveryone] [int] NULL,
    // [PostingAgent] [varchar](100) NULL,
    // [PostingCount] [int] NULL,
    // [PostingJobCount] [int] NULL,
    // [DBSource] [int] NOT NULL,
    // [Recommendedby] [int] NULL,
    // [HRurl] [varchar](200) NULL,
    // [EduInfo] [varchar](50) NULL,
    // [AgeRangeInfo] [varchar](50) NULL,
    // [IncomeInfo] [varchar](50) NULL,
    // [Gender] [varchar](50) NULL,
    // [mobilephone] [varchar](50) NULL,
    // [LastTransDate] [datetime] NULL,
    // [IsFromAgency] [bit] NULL,
    // [Recongnized] [bit] NULL,
    // [EmployerTypeConfirmed] [bit] NULL,
    // [ServiceNotification] [bit] NOT NULL,
    // [isPrimary] [bit] NULL,
    // [isJSASent] [bit] NULL,
    // CONSTRAINT [aaaaaUsers_PK] PRIMARY KEY NONCLUSTERED
    //(
    // [UserID] ASC
    //)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
    //) ON [PRIMARY]

    //GO //

  21. Dave on 21 Jul 2011

    this is excellent stuff…thanx and BIG UP man…when will other next tutorials be made available?

  22. ashish on 06 Jan 2012

    please write an article for ROLE and profiles related things.
    please please please mvc is total new and i dont find any other good articles on it rather than yours…

    THANKS

  23. yusia on 07 Jan 2012

    Thanks,thanks,thanks,thanks,thanks !!!
    Excellent tutorials,very helpful to me

  24. ckonig on 17 Feb 2012

    This is great. It really helped me to get an overview on this topic. Right now I am afraid, you did not continue with the Role Provider.
    If anyone has found a good tutorial for that, help me out!

  25. James on 16 Apr 2012

    HI, I’m just wondering whether you have had time to complete this walkthrough ? Like many others I have learnt quite a lot from your tutorial, your writing style is easy to understand.

    THanks

  26. Ted on 30 Apr 2012

    great tutorial man! made it so easy to follow.
    when is the next part coming?

  27. Ailyn on 14 May 2012

    This tutorial is awesome, congratulations.

    Please did you already wrote the tutorial about Roles and Profiles providers?

    Can you please show how the code needs to be changed to collect from the user other fields??

  28. Linus on 18 Jun 2012

    great tutorials ! I have got problems when registering new users. It throws the internal exception: “Cannot insert explicit value for identity column in table ‘Users’ when IDENTITY_INSERT is set to OFF.” at db.SaveChanges(); in userRepository.cs class. I have looked at code , there is not any value assigned to userid even though it’s throwing exception. WOuld please suggest me where I have change to set Identity_insert ON in visual studio. Please send me at email

  29. Partha Sarathy on 17 Jul 2012

    This is an amazing tutorial. Great Job mate

  30. Luis on 19 Jul 2012

    thanks please do you any projects in mvc2 please send me …i have a problem with ajax ..
    or send this project of membership …storm_sh4dow@hotmail.com

  31. Hamidnch on 07 Sep 2012

    thanks a lot.
    please send this project for me,my email is: Hamidnch2007@Gmail.Com
    Best Regards.

  32. Hamidnch on 07 Sep 2012

    This is Great nice article.
    Where is The Definition Of AddToUser Method in UserReposotory.
    If posible,Please send for me Full source code of this projects.
    Thanks a lot.
    I am Waiting so much………………………..


Leave your comment