Entity Framework Custom Membership Provider

Here's an example of a custom MembershipProvider that uses Entity Framework 4.2 for data access.

First you will need to create an aspnetdb database on your SQL Server. On the SQL Server open a command prompt and run the following command:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe

In the code below I have created a custom MembershipUser object in which I've included the following columns that I added to the aspnetdb aspnet_Users table:

Id (int, not null) Identity Column
FirstName (nvarchar(256), null)
LastName (nvarchar(256), null)

Add a connection string to your web config to connect to the aspnetdb:

<connectionStrings>
    <add name="ApplicationServices" connectionString="data source=server;Initial Catalog=aspnetdb;User Id=sa;Password=password;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
</connectionStrings>

The name of the connection string is important as it is used when declaring the EF data context.

The keys that are used to encrypt passwords and password questions need to be generated and added to the web.config. This is important if your application runs in a server farm.

<system.web>
    <machineKey validationKey="EF3DFB100FD7C5FCC8915EEC705968659FB2982C466B9D67694471F6543C60CE244E5FF8CF049C759784F73CFC2D4E05B1D75D92134C27C1DA644A5D32D82D50"
                decryptionKey="A71798F098622A67A6DFA5472AA46C8E83AF7462DA19BF8B2E23C1B113608F7F"
                validation="SHA1"
                decryption="AES"/>
</system.web>

Here is a snippet of code from a Console application that can be used to generate the 128bit and 64bit keys:

static void GenerateMachineKey(string[] argv)
        {
            int len = 128;
            if (argv.Length > 0)
                len = int.Parse(argv[0]);
            byte[] buff = new byte[len / 2];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(buff);
            StringBuilder sb = new StringBuilder(len);
            for (int i = 0; i < buff.Length; i++)
                sb.Append(string.Format("{0:X2}", buff[i]));
            Console.WriteLine(sb);
        }

Here's the Membership section from the web.config

<membership userIsOnlineTimeWindow="20" defaultProvider="EfMembershipProvider">
      <providers>
        <clear />
        <add name="EfMembershipProvider"
             type="Company.Namespace.EfMembershipProvider,Assembly" 
             connectionStringName="ApplicationServices"
             enablePasswordRetrieval="false" 
             enablePasswordReset="true" 
             requiresQuestionAndAnswer="false" 
             requiresUniqueEmail="false" 
             maxInvalidPasswordAttempts="5" 
             minRequiredPasswordLength="6" 
             minRequiredNonalphanumericCharacters="0" 
             passwordAttemptWindow="10" 
             applicationName="/facade" 
             description="Entity Framework Membership Provider" />
      </providers>
</membership>

It's important to always give an applicationName, by default this will be the web root of your application which can lead to confusion and problems when you deploy your solution to a live server environment.

You can customise the Profile and RoleManager Providers too, but I've left these as using the standard System.Web.Profile.SqlProfileProvider and System.Web.Security.SqlRoleProvider types.

Here's my data context class that defines the data sets (tables) from the aspnetdb and my custom membership provider class. The "ApplicationServices" in the constructor is the name of the connection string to the aspnetdb database from the web.config

        public class MembershipContext : DbContext
        {
            public MembershipContext()
                : base("ApplicationServices")
            { }

            public DbSet<User> UserData { get; set; }
            public DbSet<Member> MembershipData { get; set; }
            public DbSet<Application> ApplicationData { get; set; }
            public DbSet<UserRoles> UserRoleData { get; set; }
            public DbSet<Profile> ProfileData { get; set; }
        }

Here's the class file (Assembly) that defines the classes used in the data context, overrides the MembershipProvider properties and methods and implements a custom MembershipUser object to include the extra columns I added to the database.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.Web.Configuration;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Configuration;
using System.Data;
using System.Security.Cryptography;
using System.Text;
using System.Data.SqlTypes;

namespace Company.Namespace
{    
    [Table("aspnet_Users")]
    public class User
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public Guid ApplicationId { get; set; }
        [Key]
        public Guid UserId { get; set; }
        public string UserName { get; set; }
        public string LoweredUserName { get; set; }
        public string MobileAlias { get; set; }
        public bool IsAnonymous { get; set; }
        public DateTime LastActivityDate { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    [Table("aspnet_Membership")]
    public class Member
    {
        [ForeignKey("Application")]
        public Guid ApplicationId { get; set; }       
        public virtual Application Application { get; set; }
        [Key]
        [ForeignKey("User")]        
        public Guid UserId { get; set; }   
        public virtual User User { get; set; }
        public string Password { get; set; }
        public int PasswordFormat { get; set; }
        public string PasswordSalt { get; set; }
        public string MobilePIN { get; set; }
        public string Email { get; set; }
        public string LoweredEmail { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public DateTime CreateDate { get; set; }
        public DateTime LastLoginDate { get; set; }
        public DateTime LastPasswordChangedDate { get; set; }
        public DateTime LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public DateTime FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }
    }

    [Table("aspnet_Applications")]
    public class Application
    {
        public string ApplicationName { get; set; }
        public string LoweredApplicationName { get; set; }
        [Key]
        public Guid ApplicationId { get; set; }
        public string Description { get; set; }
    }

    [Table("aspnet_UsersInRoles")]
    public class UserRoles
    {
        [Key]
        [Column(Order=1)]
        public Guid UserId { get; set; }
        [Key]
        [Column(Order = 2)]
        public Guid RoleId { get; set; }
    }

    [Table("aspnet_Profile")]
    public class Profile
    {
        [Key]
        public Guid UserId { get; set; }
        public string PropertyNames { get; set; }
        public string PropertyValuesString { get; set; }
        public byte[] PropertyValuesBinary { get; set; }
        public DateTime LastUpdatedDate { get; set; }
    }

    public class EfMembershipProvider : MembershipProvider
    {
        //
        // Global connection string, generated password length, generic exception message, event log info.
        //

        private int newPasswordLength = 8;
        private string exceptionMessage = "An exception occurred. Please check the Log.";

        //
        // Used when determining encryption key values.
        //

        private MachineKeySection machineKey;

        //
        // If false, exceptions are thrown to the caller. If true,
        // exceptions are written to the event log.
        //

        private bool pWriteExceptionsToEventLog;

        public bool WriteExceptionsToEventLog
        {
            get { return pWriteExceptionsToEventLog; }
            set { pWriteExceptionsToEventLog = value; }
        }

        //
        // System.Configuration.Provider.ProviderBase.Initialize Method
        //        

        public override void Initialize(string name, NameValueCollection config)
        {
            //
            // Initialize values from web.config.
            //

            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "EfMembershipProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Entity Framework Membership Provider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);

            pApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
            pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
            pMinRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonAlphanumericCharacters"], "1"));
            pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
            pPasswordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], ""));
            pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
            pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
            pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
            pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
            pWriteExceptionsToEventLog = Convert.ToBoolean(GetConfigValue(config["writeExceptionsToEventLog"], "true"));

            string temp_format = config["passwordFormat"];
            if (temp_format == null)
            {
                temp_format = "Hashed";
            }

            switch (temp_format)
            {
                case "Hashed":
                    pPasswordFormat = MembershipPasswordFormat.Hashed;
                    break;
                case "Encrypted":
                    pPasswordFormat = MembershipPasswordFormat.Encrypted;
                    break;
                case "Clear":
                    pPasswordFormat = MembershipPasswordFormat.Clear;
                    break;
                default:
                    throw new ProviderException("Password format not supported.");
            }            

            // Get encryption and decryption key information from the configuration.
            //Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            //machineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey");
            machineKey = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey");

            if (machineKey.ValidationKey.Contains("AutoGenerate"))
                if (PasswordFormat != MembershipPasswordFormat.Clear)
                    throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys.");
        }

        //
        // A helper function to retrieve config values from the configuration file.
        //

        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
                return defaultValue;

            return configValue;
        }

        //
        // System.Web.Security.MembershipProvider properties.
        //

        private string pApplicationName;
        private bool pEnablePasswordReset;
        private bool pEnablePasswordRetrieval;
        private bool pRequiresQuestionAndAnswer;
        private bool pRequiresUniqueEmail;
        private int pMaxInvalidPasswordAttempts;
        private int pPasswordAttemptWindow;
        private MembershipPasswordFormat pPasswordFormat;

        public override string ApplicationName
        {
            get { return pApplicationName; }
            set { pApplicationName = value; }
        }

        public override bool EnablePasswordReset
        {
            get { return pEnablePasswordReset; }
        }


        public override bool EnablePasswordRetrieval
        {
            get { return pEnablePasswordRetrieval; }
        }


        public override bool RequiresQuestionAndAnswer
        {
            get { return pRequiresQuestionAndAnswer; }
        }


        public override bool RequiresUniqueEmail
        {
            get { return pRequiresUniqueEmail; }
        }


        public override int MaxInvalidPasswordAttempts
        {
            get { return pMaxInvalidPasswordAttempts; }
        }


        public override int PasswordAttemptWindow
        {
            get { return pPasswordAttemptWindow; }
        }


        public override MembershipPasswordFormat PasswordFormat
        {
            get { return pPasswordFormat; }
        }

        private int pMinRequiredNonAlphanumericCharacters;

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return pMinRequiredNonAlphanumericCharacters; }
        }

        private int pMinRequiredPasswordLength;

        public override int MinRequiredPasswordLength
        {
            get { return pMinRequiredPasswordLength; }
        }

        private string pPasswordStrengthRegularExpression;

        public override string PasswordStrengthRegularExpression
        {
            get { return pPasswordStrengthRegularExpression; }
        }

        //
        // System.Web.Security.MembershipProvider methods.
        //

        //
        // MembershipProvider.ChangePassword
        //

        public override bool ChangePassword(string username, string oldPwd, string newPwd)
        {
            if (!ValidateUser(username, oldPwd))
                return false;

            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, newPwd, true);

            OnValidatingPassword(args);

            if (args.Cancel)
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Change password canceled due to new password validation failure.");

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                          && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.Password = EncodePassword(newPwd);
                        member.LastPasswordChangedDate = DateTime.Now;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.ChangePasswordQuestionAndAnswer
        //

        public override bool ChangePasswordQuestionAndAnswer(string username,
                      string password,
                      string newPwdQuestion,
                      string newPwdAnswer)
        {
            if (!ValidateUser(username, password))
                return false;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                          && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.PasswordQuestion = newPwdQuestion;
                        member.PasswordAnswer = newPwdAnswer;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePasswordQuestionAndAnswer");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return false;
        }

        //
        // MembershipProvider.CreateUser
        //

        public override MembershipUser CreateUser(string username,
                 string password,
                 string email,
                 string passwordQuestion,
                 string passwordAnswer,
                 bool isApproved,
                 object providerUserKey,
                 out MembershipCreateStatus status)
        {
            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, password, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            MembershipUser u = GetUser(username, false);

            if (u == null)
            {
                DateTime createDate = DateTime.Now;

                if (providerUserKey == null)
                {
                    providerUserKey = Guid.NewGuid();
                }
                else
                {
                    if (!(providerUserKey is Guid))
                    {
                        status = MembershipCreateStatus.InvalidProviderUserKey;
                        return null;
                    }
                }

                Application application = null;

                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    application = context.ApplicationData
                        .Where(a => a.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                }

                User user = new User()
                {
                    UserId = (Guid)providerUserKey,
                    UserName = username,
                    LoweredUserName = username.ToLower(),
                    LastActivityDate = createDate,
                    ApplicationId = application.ApplicationId
                };

                Member member = new Member()
                {
                    Password = EncodePassword(password),
                    Email = email,
                    PasswordQuestion = passwordQuestion,
                    PasswordAnswer = EncodePassword(passwordAnswer),
                    PasswordSalt = CreateSalt(),
                    IsApproved = isApproved,                    
                    Comment = string.Empty,
                    CreateDate = createDate,
                    LastPasswordChangedDate = createDate,
                    IsLockedOut = false,
                    FailedPasswordAttemptCount = 0,
                    FailedPasswordAttemptWindowStart = createDate,
                    FailedPasswordAnswerAttemptCount = 0,
                    FailedPasswordAnswerAttemptWindowStart = createDate,
                    Application = application,
                    LastLockoutDate = SqlDateTime.MinValue.Value,
                    LastLoginDate = SqlDateTime.MinValue.Value,
                    User = user
                };

                try
                {
                    using (Data.MembershipContext context = new Data.MembershipContext())
                    {
                        context.Entry(member).State = EntityState.Added;
                        context.Entry(member.Application).State = EntityState.Unchanged;
                        context.SaveChanges();
                        status = MembershipCreateStatus.Success;
                    }
                }
                catch (Exception e)
                {
                    if (WriteExceptionsToEventLog)
                    {
                        WriteToEventLog(e, "CreateUser");
                    }
                    status = MembershipCreateStatus.ProviderError;
                }
                return GetUser(username, false);
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }
            return null;
        }

        //
        // MembershipProvider.DeleteUser
        //

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        User user = context.UserData.Attach(member.User);
                        
                        //Delete Roles
                        var userRoles = context.UserRoleData
                            .Where(ur => ur.UserId.Equals(user.UserId))
                            .ToList();
                        userRoles.ForEach(ur => context.UserRoleData.Remove(ur));
                        context.SaveChanges();
                        
                        //Delete Profile
                        var userProfiles = context.ProfileData
                            .Where(p => p.UserId.Equals(user.UserId))
                            .ToList();
                        userProfiles.ForEach(p => context.ProfileData.Remove(p));
                        context.SaveChanges();

                        context.Entry(user).State = EntityState.Deleted;
                        context.Entry(member).State = EntityState.Deleted;                                                                     
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "DeleteUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.GetAllUsers
        //

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.Application.ApplicationName.Equals(ApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);
                    totalRecords = members.Count();
                    foreach (Member member in members)
                    {
                        EfMembershipUser user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;
                        users.Add(user);
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetAllUsers ");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }       

        //
        // MembershipProvider.GetNumberOfUsersOnline
        //

        public override int GetNumberOfUsersOnline()
        {
            TimeSpan onlineSpan = new TimeSpan(0, System.Web.Security.Membership.UserIsOnlineTimeWindow, 0);
            DateTime compareTime = DateTime.Now.Subtract(onlineSpan);
            int numOnline = 0;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    numOnline = context.MembershipData
                        .Where(m => m.User.LastActivityDate > compareTime
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .Count();
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetNumberOfUsersOnline");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return numOnline;
        }

        //
        // MembershipProvider.GetPassword
        //

        public override string GetPassword(string username, string answer)
        {
            string password = string.Empty;
            string passwordAnswer = string.Empty;

            if (!EnablePasswordRetrieval)
            {
                throw new ProviderException("Password Retrieval Not Enabled.");
            }

            if (PasswordFormat == MembershipPasswordFormat.Hashed)
            {
                throw new ProviderException("Cannot retrieve Hashed passwords.");
            }

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username) &&
                            m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        if (member.IsLockedOut)
                            throw new MembershipPasswordException("The supplied user is locked out.");
                        password = member.Password;
                        passwordAnswer = member.PasswordAnswer;
                    }
                    else
                    {
                        throw new MembershipPasswordException("The supplied user name is not found.");
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            if (RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
            {
                UpdateFailureCount(username, "passwordAnswer");

                throw new MembershipPasswordException("Incorrect password answer.");
            }


            if (PasswordFormat == MembershipPasswordFormat.Encrypted)
            {
                password = UnEncodePassword(password);
            }

            return password;
        }

        //
        // MembershipProvider.GetUser(string, bool)
        //

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            EfMembershipUser user = null;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;

                        if (userIsOnline)
                        {
                            member.User.LastActivityDate = DateTime.Now;
                            context.Entry(member).State = EntityState.Modified;
                            context.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(String, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return user;
        }

        //
        // MembershipProvider.GetUser(object, bool)
        //

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            EfMembershipUser user = null;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    Member member = context.MembershipData
                        .Where(m => m.User.UserId.Equals((Guid)providerUserKey)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();

                    if (member != null)
                    {
                        user = new EfMembershipUser(
                            Name,
                            member.User.UserName,
                            member.User.UserId,
                            member.Email,
                            member.PasswordQuestion,
                            member.Comment,
                            member.IsApproved,
                            member.IsLockedOut,
                            member.CreateDate,
                            member.LastLoginDate,
                            member.User.LastActivityDate,
                            member.LastPasswordChangedDate,
                            member.LastLockoutDate);
                        user.Id = member.User.Id;
                        user.FirstName = member.User.FirstName;
                        user.LastName = member.User.LastName;

                        if (userIsOnline)
                        {
                            member.User.LastActivityDate = DateTime.Now;
                            context.Entry(member).State = EntityState.Modified;
                            context.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(String, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return user;
        }

        //
        // MembershipProvider.UnlockUser
        //

        public override bool UnlockUser(string username)
        {
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.IsLockedOut = false;
                        member.LastLockoutDate = DateTime.Now;
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UnlockUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return false;
        }

        //
        // MembershipProvider.GetUserNameByEmail
        //

        public override string GetUserNameByEmail(string email)
        {
            string username = string.Empty;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.Email.Equals(email) && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        username = member.User.UserName;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUserNameByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }

            return username;
        }

        //
        // MembershipProvider.ResetPassword
        //

        public override string ResetPassword(string username, string answer)
        {
            string passwordAnswer = "";

            if (!EnablePasswordReset)
            {
                throw new NotSupportedException("Password reset is not enabled.");
            }

            if (answer == null && RequiresQuestionAndAnswer)
            {
                UpdateFailureCount(username, "passwordAnswer");

                throw new ProviderException("Password answer required for password reset.");
            }

            string newPassword =
              System.Web.Security.Membership.GeneratePassword(newPasswordLength, MinRequiredNonAlphanumericCharacters);

            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Reset password canceled due to password validation failure.");

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        if (member.IsLockedOut)
                            throw new MembershipPasswordException("The supplied user is locked out.");

                        passwordAnswer = member.PasswordAnswer;
                    }
                    else
                    {
                        throw new MembershipPasswordException("The supplied user name is not found.");
                    }

                    if (RequiresQuestionAndAnswer && !CheckPassword(answer, passwordAnswer))
                    {
                        UpdateFailureCount(username, "passwordAnswer");

                        throw new MembershipPasswordException("Incorrect password answer.");
                    }

                    member.Password = EncodePassword(newPassword);
                    member.LastPasswordChangedDate = DateTime.Now;
                    context.Entry(member).State = EntityState.Modified;
                    context.SaveChanges();
                    return newPassword;
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ResetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }

        //
        // MembershipProvider.UpdateUser
        //

        public override void UpdateUser(MembershipUser user)
        {
            EfMembershipUser efUser = (EfMembershipUser)user;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserId.Equals((Guid)efUser.ProviderUserKey))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.Comment = efUser.Comment;
                        member.Email = efUser.Email;
                        member.User.FirstName = efUser.FirstName;
                        member.IsApproved = efUser.IsApproved;
                        member.User.LastName = efUser.LastName;
                        context.SaveChanges();
                    }                    
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }
       
        //
        // MembershipProvider.ValidateUser
        //

        public override bool ValidateUser(string username, string password)
        {
            bool isValid = false;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName)
                            && m.IsLockedOut == false)
                        .FirstOrDefault();
                    if (member != null)
                    {
                        member.LastLoginDate = DateTime.Now;
                        if (member.IsApproved)
                        {
                            if (CheckPassword(password, member.Password))
                            {
                                isValid = true;
                                context.Entry(member).State = EntityState.Modified;
                                context.SaveChanges();
                            }
                            else
                            {
                                UpdateFailureCount(username, "password");
                            }
                        }
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ValidateUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return isValid;
        }


        //
        // UpdateFailureCount
        //   A helper method that performs the checks and updates associated with
        // password failure tracking.
        //

        private void UpdateFailureCount(string username, string failureType)
        {
            DateTime windowStart = new DateTime();
            int failureCount = 0;

            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var member = context.MembershipData
                        .Where(m => m.User.UserName.Equals(username)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .FirstOrDefault();
                    if (member != null)
                    {
                        if (failureType == "password")
                        {
                            failureCount = member.FailedPasswordAttemptCount;
                            windowStart = member.FailedPasswordAttemptWindowStart;
                        }

                        if (failureType == "passwordAnswer")
                        {
                            failureCount = member.FailedPasswordAnswerAttemptCount;
                            windowStart = member.FailedPasswordAttemptWindowStart;
                        }

                        DateTime windowEnd = windowStart.AddMinutes(PasswordAttemptWindow);

                        if (failureCount == 0 || DateTime.Now > windowEnd)
                        {
                            // First password failure or outside of PasswordAttemptWindow. 
                            // Start a new password failure count from 1 and a new window starting now.

                            if (failureType == "password")
                            {
                                member.FailedPasswordAttemptCount = 1;
                                member.FailedPasswordAttemptWindowStart = DateTime.Now;
                            }

                            if (failureType == "passwordAnswer")
                            {
                                member.FailedPasswordAnswerAttemptCount = 1;
                                member.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
                            }
                        }
                        else
                        {
                            if (failureCount++ >= MaxInvalidPasswordAttempts)
                            {
                                // Password attempts have exceeded the failure threshold. Lock out
                                // the user.

                                member.IsLockedOut = true;
                                member.LastLockoutDate = DateTime.Now;
                            }
                            else
                            {
                                if (failureType == "password")
                                    member.FailedPasswordAttemptCount = failureCount;

                                if (failureType == "passwordAnswer")
                                    member.FailedPasswordAnswerAttemptCount = failureCount;
                            }
                        }
                        context.Entry(member).State = EntityState.Modified;
                        context.SaveChanges();
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateFailureCount");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
        }

        //
        // CheckPassword
        //   Compares password values based on the MembershipPasswordFormat.
        //

        private bool CheckPassword(string password, string dbpassword)
        {
            string pass1 = password;
            string pass2 = dbpassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Encrypted:
                    pass2 = UnEncodePassword(dbpassword);
                    break;
                case MembershipPasswordFormat.Hashed:
                    pass1 = EncodePassword(password);
                    break;
                default:
                    break;
            }

            if (pass1 == pass2)
            {
                return true;
            }

            return false;
        }


        //
        // EncodePassword
        //   Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
        //

        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            if (!string.IsNullOrEmpty(password))
            {
                switch (PasswordFormat)
                {
                    case MembershipPasswordFormat.Clear:
                        break;
                    case MembershipPasswordFormat.Encrypted:
                        encodedPassword =
                          Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                        break;
                    case MembershipPasswordFormat.Hashed:
                        HMACSHA1 hash = new HMACSHA1();
                        hash.Key = HexToByte(machineKey.ValidationKey);
                        encodedPassword =
                          Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                        break;
                    default:
                        throw new ProviderException("Unsupported password format.");
                }
            }

            return encodedPassword;
        }


        //
        // UnEncodePassword
        //   Decrypts or leaves the password clear based on the PasswordFormat.
        //

        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    password =
                      Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }

        //
        // HexToByte
        //   Converts a hexadecimal string to a byte array. Used to convert encryption
        // key values from the configuration.
        //

        private byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        /// <summary>
        /// Creates a random 128 password salt
        /// </summary>
        /// <returns></returns>
        private static string CreateSalt()
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buff = new byte[32];
            rng.GetBytes(buff);

            return Convert.ToBase64String(buff);
        }


        //
        // MembershipProvider.FindUsersByName
        //

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();
            EfMembershipUser user = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.User.UserName.StartsWith(usernameToMatch.Replace("%", ""))
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);

                    totalRecords = members.Count();

                    foreach(Member m in members.ToList())
                    {
                        user = new EfMembershipUser(
                            Name,
                            m.User.UserName,
                            m.User.UserId,
                            m.Email,
                            m.PasswordQuestion,
                            m.Comment,
                            m.IsApproved,
                            m.IsLockedOut,
                            m.CreateDate,
                            m.LastLoginDate,
                            m.User.LastActivityDate,
                            m.LastPasswordChangedDate,
                            m.LastLockoutDate);
                        user.Id = m.User.Id;
                        user.FirstName = m.User.FirstName;
                        user.LastName = m.User.LastName;
                        users.Add(user);
                    }                    
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByName");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }
       
        //
        // MembershipProvider.FindUsersByEmail
        //

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            int startIndex = pageSize * pageIndex;
            MembershipUserCollection users = new MembershipUserCollection();
            EfMembershipUser user = null;
            try
            {
                using (Data.MembershipContext context = new Data.MembershipContext())
                {
                    var members = context.MembershipData
                        .Where(m => m.Email.Contains(emailToMatch)
                            && m.Application.ApplicationName.Equals(pApplicationName))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startIndex)
                        .Take(pageSize);

                    totalRecords = members.Count();

                    foreach (Member m in members.ToList())
                    {
                        user = new EfMembershipUser(
                            Name,
                            m.User.UserName,
                            m.User.UserId,
                            m.Email,
                            m.PasswordQuestion,
                            m.Comment,
                            m.IsApproved,
                            m.IsLockedOut,
                            m.CreateDate,
                            m.LastLoginDate,
                            m.User.LastActivityDate,
                            m.LastPasswordChangedDate,
                            m.LastLockoutDate);
                        user.Id = m.User.Id;
                        user.FirstName = m.User.FirstName;
                        user.LastName = m.User.LastName;
                        users.Add(user);
                    }          
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw e;
                }
            }
            return users;
        }

        //
        // WriteToEventLog
        //   A helper function that writes exception detail to the event log. Exceptions
        // are written to the event log as a security measure to avoid private database
        // details from being returned to the browser. If a method does not return a status
        // or boolean indicating the action succeeded or failed, a generic exception is also 
        // thrown by the caller.
        //

        private void WriteToEventLog(Exception e, string action)
        {
            string message = action + ": An exception occurred communicating with the data source.\n\n";            
            Log.LogException(message, e, LogCategory.Membership, System.Diagnostics.TraceEventType.Error);
         
        }
    }

    public class EfMembershipUser : MembershipUser
    {
        public int Id {get;set;}
        public string FirstName {get;set;}        
        public string LastName {get;set;}        

        public EfMembershipUser(
            string providername,
            string username,
            object providerUserKey,
            string email,
            string passwordQuestion,
            string comment,
            bool isApproved,
            bool isLockedOut,
            DateTime creationDate,
            DateTime lastLoginDate,
            DateTime lastActivityDate,
            DateTime lastPasswordChangedDate,
            DateTime lastLockedOutDate) :
            base(
                providername,
                username,
                providerUserKey,
                email,
                passwordQuestion,
                comment,
                isApproved,
                isLockedOut,
                creationDate,
                lastLoginDate,
                lastActivityDate,
                lastPasswordChangedDate,
                lastLockedOutDate) { }

        public EfMembershipUser() { }

        public void Delete(EfMembershipUser User)
        {
            User user = GetUserById(User.Id);
            Membership.DeleteUser(user.UserName);
        }

        public void Update(EfMembershipUser User)
        {
            throw new NotImplementedException();
        }

        public static User GetUserById(int Id)
        {
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                return context.UserData
                    .Where(u => u.Id.Equals(Id))
                    .FirstOrDefault();
            }
        }

        public List<EfMembershipUser> Paged(int maximumRows, int startRowIndex, string search, string filter)
        {
            EfMembershipUser user = new EfMembershipUser();
            List<EfMembershipUser> users = new List<EfMembershipUser>();
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                List<Member> members = new List<Member>();
                if (!string.IsNullOrEmpty(search))
                {                    
                    members = context.MembershipData
                        .Where(m => m.User.UserName.Contains(search))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startRowIndex)
                        .Take(maximumRows)                        
                        .ToList();                    
                }
                else if (!string.IsNullOrEmpty(filter))
                {
                    members = context.MembershipData
                        .Where(m => m.User.UserName.StartsWith(filter))
                        .OrderBy(m => m.User.UserName)
                        .Skip(startRowIndex)
                        .Take(maximumRows)
                        .ToList();
                }
                else
                {
                    members = context.MembershipData
                       .OrderBy(m => m.User.UserName)
                       .Skip(startRowIndex)
                       .Take(maximumRows)
                       .ToList();
                }
                foreach (Member member in members)
                {
                    user = new EfMembershipUser("EfMembershipProvider",
                        member.User.UserName,
                        member.User.UserId,
                        member.Email,
                        member.PasswordQuestion,
                        member.Comment,
                        member.IsApproved,
                        member.IsLockedOut,
                        member.CreateDate,
                        member.LastLoginDate,
                        member.User.LastActivityDate,
                        member.LastPasswordChangedDate,
                        member.LastLockoutDate);
                    user.Id = member.User.Id;
                    user.FirstName = member.User.FirstName;
                    user.LastName = member.User.LastName; 
                    users.Add(user);
                }                    
            }
            return users;
        }

        public int Count(string search, string filter)
        {
            using (Data.MembershipContext context = new Data.MembershipContext())
            {
                if (!string.IsNullOrEmpty(search))
                {
                    return context.UserData
                        .Where(u => u.UserName.Contains(search))
                        .Count();
                }
                else if (!string.IsNullOrEmpty(filter))
                {
                    return context.UserData
                        .Where(u => u.UserName.StartsWith(filter))
                        .Count();
                }
                else
                {
                    return context.UserData
                        .Count();
                }
            }
        } 
    }
}

I've written a small wrapper for the Enterprise Library database log which is included in the Solution but not seen here.

There is also a Unit Test project with about 30 tests for the custom membership provider.

6 comments:

Ben Swayne said...

This article looks great! (Haven't test the code yet, but I appreciate the detail you've put into it).

However, why do you start the article with running aspnet_regsql.exe? Isn't the point of an EF membership provider to avoid using the "old" db schema?

Ian Chivers said...

Hi Ben,

I used aspnet_regsql to create the aspnetdb database. I know that someone as created a code first context and generated the database from the code without any issues.

Thanks for the feedback,
Ian.

Unknown said...

I am using this code in my project. But still I am not able to figure out that how I can add new fields in "aspnet_Users" because in create user overridden function you dint mentioned new fields like FirstName and LastName in your case...

Anonymous said...

You saved a lot of my time. Thanks you! That is realy great.

Cesar V. Ramirez said...

Hi Ian,

Wow! Really great stuff!

And I see you mention a "Solution" above... but I'm not seeing it. Am I totally just missing it? If it is available, will you please point me to that? I'd really appreciate it. And also, if the EF Custom Profile Provider solution and/or sample project, that would be great.

-best
Cesar

Unknown said...

đồng tâm
game mu
cho thuê nhà trọ
cho thuê phòng trọ
nhac san cuc manh
số điện thoại tư vấn pháp luật miễn phí
văn phòng luật
tổng đài tư vấn pháp luật
dịch vụ thành lập công ty trọn gói
nước cờ trên bàn thương lượng
mbp
erg
nghịch lý
chi square test
nghệ thuật nói chuyện
coase
thuyết kỳ vọng
chiến thắng con quỷ trong bạn
cân bằng nash

- Anh đánh người là sự thật, mà bên các người không ai bị thương. Tôi đề nghị các người trả tiền thuốc men, thiếu một việc là được mà. Sao phải khổ chứ?
Khương ca lúc này bớt men say nên nói chuyện cũng khách khí hơn.

- Tôi đây là phòng vệ chính đáng.
Triệu Quốc Đống biết đối phương đã yêu cầu mình bồi thường viện phí thì tin rằng thằng thanh niên kia có lai lịch.

- Tự vệ có thể, nhưng các người có ai bị thương không? Không có. Đối phương cũng nói do uống nhiều rượu nên lảo đảo và vô thức xé rách áo. Về phần cô gái này, cô lúc đầu không phải uống rượu cùng bọn họ sao? Đối phương không ép cô làm gì, cô tự nguyên uống với bọn họ, cho dù là xung đột về lời nói cũng không có vấn đề gì. Các người sao có thể ra tay đánh người?

Triệu Quốc Đống thấy tên Khương ca này nói giỏi như vậy. Nói chuyện đầy lý lẽ, đây là cái bẫy gài hắn vào.

- Sếp Khương, có phải đối phương có quan hệ đúng không?
Triệu Quốc Đống lúc này cũng thấy tên Trưởng đồn này không đơn giản, cũng không yêu cầu nhiều, chỉ khuyên hắn dùng tiền mua bình yên.

- Cậu hiểu như thế nào là việc của cậu. Tôi chỉ luận việc nói việc mà thôi. Cậu đánh người, cậu nói mình tự vệ, đối phương phủ nhận, Bảo vệ ở khách sạn cũng không phát hiện, cậu nói tôi xử lý như thế nào?
Cù Vận Bạch lặng lẽ kéo áo Triệu Quốc Đống. Triệu Quốc Đống biết Cù Vận Bạch không muốn làm lớn chuyện. Nếu chuyện truyền ra sẽ ảnh hưởng không tốt tới hình tượng của Cù Vận Bạch, Đồng Úc và Triệu Quốc Đống. Nhất là nữ cán bộ như Cù Vận Bạch càng đáng sợ hơn.

Triệu Quốc Đống mặc dù rất khó chịu nhưng vẫn phải đồng ý:
- Được, anh nói bọn họ đòi bao tiền?

- Đơn giản, bọn họ không bị thương nặng, theo tôi đoán cũng chỉ là đi chụp Ct một chút mà thôi, sau đó kê chút đơn thuốc là được. Tôi thấy các anh trả ba ngàn đi.
Trưởng đồn Khương nói.

Ba ngàn? Triệu Quốc Đống và Cù Vận Bạch ngẩn ra, Đồng Úc há hốc mồm đầy sợ hãi.

Ba ngàn? Triệu Quốc Đống chưa nói chuyện, Cù Vận Bạch đã nói:
- Bọn họ đây là muốn lừa tiền sao? Chúng tôi không trả, sao phải cho bọn chúng? Còn có công lý không? Kẻ phi pháp được bảo vệ, người bị hại còn phải bù tiền.