Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Bendik Engebretsen 105 posts 202 karma points
    Jan 12, 2016 @ 13:43
    Bendik Engebretsen
    0

    Custom membership provider

    Hi,

    I am about to try and replace the built-in membership mechanism in Umbraco 7 with my own. The new mechanism is based on a very simple database structure in an MS Access Database (which will later on be replaced by an other, more solid external database.) So I have a table with usernames and passwords and want to use these for member authentication in v7.

    I have been trying to find some good examples (with code) of how to implement this, but I seem to always end up with articles where people are having trouble;-)

    Would anyone with experience in implementing this care to share some insight and code? I would prefer to go the RoleProvider/MembershipProvider way and avoid ASP.NET Identity, which is very new in Umbraco and seems to be an overkill for my limited project here.

    Bendik

  • Ian 178 posts 752 karma points
    Jan 12, 2016 @ 15:42
    Ian
    1

    Hi Bendik

    You would need to create one class which inherits from MembershipProvider - see this for an example which may work for MS Access https://msdn.microsoft.com/en-us/library/6tc47t75.aspx

    Another class if you want to implement roles. See here for an Odbc example https://msdn.microsoft.com/en-us/library/317sza4k.aspx

    in your site web.config the 'membership' section might look like this.

    <membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <add name="UmbracoMembershipProvider" type="Samples.AspNet.Membership.MembershipProvider" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="_umbracoSystemDefaultProtectType" passwordFormat="Hashed" />
        <add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
      </providers>
    </membership>
    

    The Important thing to note is that I found it easier to keep the name as 'UmbracoMembershipProvider' but change the type to your implementation

    The roleManager section would then look like this.

    <roleManager enabled="true" defaultProvider="OdbcRoleProvider">
      <providers>
        <clear />
        <add name="OdbcRoleProvider" type="Samples.AspNet.Roles.RoleProvider" />
        <add name="UmbracoRoleProvider" type="Umbraco.Web.Security.Providers.MembersRoleProvider" />
      </providers>
    </roleManager>
    

    I should also put forward a disclaimer that when I implemented membership it was not using MS Access so cannot really account for any of the odbc stuff in the examples provided.

    I did not even have access to any concrete user store at all as I had to authenticate against an external service which was not identity aware and only provided a rest interface. For that reason my values for password retrieval and reset in the web config were both false.

    Whilst all methods in the examples provided need to be present in your implementation as I found it is not necessary to implement logic for all of them to get a basic login system up an running, the main methods needing logic are

    MembershipProvider: GetUser & ValidateUser

    RoleProvider: GetAllRoles & GetRolesForUser

    Hope that helps

  • Bendik Engebretsen 105 posts 202 karma points
    Jan 13, 2016 @ 08:47
    Bendik Engebretsen
    0

    Thanks Ian!

    I actually figured out the basics myself, and I ended up with something slightly different from the Microsoft sample:

    I inherited my RoleProvider and MembershipProvider from the Umbraco ones. Because, then I could "get away" with overriding just the methods that need my logic in them. Hence much less code, since my logic is very simple.

    Also, instead of using ODBC directly, I used PetaPoco (included with Umbraco 7) and Repositories to access my database, which gives a much simpler code.

    Here are some snippets:

    UserRepository.cs

        public class UserRepository
    {
        private readonly Database _database;
    
        public UserRepository()
        {
            _database = new Database("NewPixDatabase");
        }
    
        public IList<User> GetAll()
        {
            return _database.Fetch<User>("SELECT * FROM [User] ORDER BY Username Desc");
        }
    
        public IList<User> GetByUsername(string username)
        {
            return _database.Fetch<User>(string.Format("SELECT * FROM [User] WHERE Username = '{0}' ORDER BY Username Desc", username));
        }
    
        public IList<User> GetByEmail(string email)
        {
            return _database.Fetch<User>(string.Format("SELECT * FROM [User] WHERE Email = '{0}' ORDER BY Username Desc", email));
        }
    
        public User GetUser(string username)
        {
            return _database.First<User>(string.Format("SELECT * FROM [User] WHERE Username = '{0}' ORDER BY Username Desc", username));
        }
    
        public void Update(User user)
        {
            _database.Update(user);
        }
    }
    

    MyMembersMembershipProvider.cs

    public class MyMembersMembershipProvider : Umbraco.Web.Security.Providers.MembersMembershipProvider
    {
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }
        public override bool ValidateUser(string username, string password)
        {
            var userRepository = new NewPixEditCMSApp.UserRepository();
    
            User user = userRepository.GetUser(username);
    
            if (user != null)
            {
                if (password == user.Password)
                    return true;
            }
            return false;
        }
    

    MyMembersRoleProvider.cs

    public class MyMembersRoleProvider : Umbraco.Web.Security.Providers.MembersRoleProvider
    {
        public override string[] GetAllRoles()
        {
            return new[] { "Standard" };
        }
    
        public override bool IsUserInRole(string username, string roleName)
        {
            if (roleName.ToLower() == "standard")
                return true;
            else
                return base.IsUserInRole(username, roleName);
        }
        public override string[] GetRolesForUser(string username)
        {
            return new[] { "Standard" };
        }
    }
    

    As one can see, the logic is extremely basic, only one role, "Standard", member login validation and the possibility to change the password.

    And by the way, in case someone is struggling with the connection string for MS Access:

        <add name="YourConnectionStringName" connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\YourAccessDatabase.accdb" providerName="System.Data.OleDb" />
    

    Maybe this can help others trying to roll their own membership mechanism in Umbraco 7.

  • Ian 178 posts 752 karma points
    Jan 13, 2016 @ 09:01
    Ian
    1

    Good.to hear PetaPoco is ok for use with ms access i agree there was a hell of a lot of code in those methods otherwise

  • Bendik Engebretsen 105 posts 202 karma points
    Feb 05, 2016 @ 10:09
    Bendik Engebretsen
    1

    Turns out I was too quick in my conclusions. Inheriting from Umbraco.Web.Security.Providers.MembersMembershipProvider and trying to get away with overriding only a few methods has proved to be an insuffcient implementation. I discovered there were still "links" to the content of the integrated membership database in Umbraco. So f.ex. if I changed a username in my Access database, it did not apply. I would have to do the change in Umbraco as well (which already had "duplicates" of all the users).

    Anyway, now I have ended up inheriting from System.Configuration.Provider.MembershipProvider as you do in your example. But PetaPoco still works well, so I thought I'd share my code in case anyone else is interested.

    MyMembersMembershipProvider.cs:

    namespace NewPixEditCMSApp
    {
        public class MyMembersMembershipProvider : MembershipProvider
        {
            private string _ApplicationName = "UmbracoMembershipProvider";
    
            public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
            {
                base.Initialize(name, config);
            }
    
            public override string ApplicationName
            {
                get { return _ApplicationName; }
                set
                {
                    if (string.IsNullOrEmpty(value))
                        throw new ProviderException("ApplicationName Cannot be Empty");
    
                    if (value.Length > 0x100)
                        throw new ProviderException("Provider application name too long");
    
                    _ApplicationName = value;
                }
            }
    
            public override bool ChangePassword(string username, string oldPassword, string newPassword)
            {
                throw new NotImplementedException();
            }
    
            public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
            {
                throw new NotImplementedException();
            }
    
            public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
                if (!userRepository.CreateUser(username, password, email))
                {
                    status = MembershipCreateStatus.UserRejected;
                    return null;
                }
                else
                {
                    status = MembershipCreateStatus.Success;
                    return new MembershipUser(_ApplicationName, username, username, email, string.Empty, string.Empty, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
                }
            }
    
            public override bool DeleteUser(string username, bool deleteAllRelatedData)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
                return userRepository.DeleteUser(username);
            }
    
            public override bool EnablePasswordReset
            {
                get
                {
                    return false;
                }
            }
    
            public override bool EnablePasswordRetrieval
            {
                get
                {
                    return true;
                }
            }
    
            public override int GetNumberOfUsersOnline()
            {
                throw new NotImplementedException();
            }
    
            public override string GetPassword(string username, string answer)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                User user = userRepository.GetUser(username);
    
                if (user != null)
                    return user.Password;
                else
                    return null;
            }
    
            public override int MaxInvalidPasswordAttempts
            {
                get
                {
                    return 10;
                }
            }
    
            public override int MinRequiredNonAlphanumericCharacters
            {
                get
                {
                    return 0;
                }
            }
    
            public override int MinRequiredPasswordLength
            {
                get
                {
                    return 6;
                }
            }
    
            public override int PasswordAttemptWindow
            {
                get { throw new NotImplementedException(); }
            }
    
            public override MembershipPasswordFormat PasswordFormat
            {
                get
                {
                    return MembershipPasswordFormat.Clear;
                }
            }
    
            public override string PasswordStrengthRegularExpression
            {
                get { throw new NotImplementedException(); }
            }
    
            public override bool RequiresQuestionAndAnswer
            {
                get
                {
                    return false;
                }
            }
    
            public override bool RequiresUniqueEmail
            {
                get
                {
                    return true;
                }
            }
    
            public override string ResetPassword(string username, string answer)
            {
                throw new NotImplementedException();
            }
    
            public override bool UnlockUser(string userName)
            {
                throw new NotImplementedException();
            }
    
            public override void UpdateUser(MembershipUser user)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
                User _user = new User() { Username = user.UserName, Password = user.GetPassword(), Email = user.Email, Company = user.Comment };
                userRepository.Update(_user);
            }
    
            public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                IList<User> users = userRepository.GetAll();
                totalRecords = users.Count;
    
                MembershipUserCollection membershipUsers = new MembershipUserCollection();
    
                int counter = 0;
                int startIndex = pageSize * pageIndex;
                int endIndex = startIndex + pageSize - 1;
    
                foreach (User user in users)
                {
                    if (counter >= startIndex)
                    {
                        MembershipUser membershipUser = new MembershipUser(_ApplicationName, user.Username, user.Username, user.Email, string.Empty, user.Company, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
                        membershipUsers.Add(membershipUser);
                    }
    
                    if (counter >= endIndex)
                        break;
    
                    counter++;
                }
    
                return membershipUsers;
            }
    
            public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                IList<User> users = userRepository.GetByUsername(usernameToMatch);
                totalRecords = users.Count;
    
                MembershipUserCollection membershipUsers = new MembershipUserCollection();
    
                int counter = 0;
                int startIndex = pageSize * pageIndex;
                int endIndex = startIndex + pageSize - 1;
    
                foreach (User user in users)
                {
                    if (counter >= startIndex)
                    {
                        MembershipUser membershipUser = new MembershipUser(_ApplicationName, user.Username, user.Username, user.Email, string.Empty, user.Company, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
                        membershipUsers.Add(membershipUser);
                    }
    
                    if (counter >= endIndex)
                        break;
    
                    counter++;
                }
    
                return membershipUsers;
            }
    
            public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                IList<User> users = userRepository.GetByEmail(emailToMatch);
                totalRecords = users.Count;
    
                MembershipUserCollection membershipUsers = new MembershipUserCollection();
    
                int counter = 0;
                int startIndex = pageSize * pageIndex;
                int endIndex = startIndex + pageSize - 1;
    
                foreach (User user in users)
                {
                    if (counter >= startIndex)
                    {
                        MembershipUser membershipUser = new MembershipUser(_ApplicationName, user.Username, user.Username, user.Email, string.Empty, user.Company, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
                        membershipUsers.Add(membershipUser);
                    }
    
                    if (counter >= endIndex)
                        break;
    
                    counter++;
                }
    
                return membershipUsers;
            }
    
            public override bool ValidateUser(string username, string password)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                User user = userRepository.GetUser(username);
    
                if (user != null)
                {
                    if (password == user.Password)
                        return true;
                }
                return false;
            }
    
            public override MembershipUser GetUser(string username, bool userIsOnline)
            {
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                User user = userRepository.GetUser(username);
    
                if (user != null)
                    return new MembershipUser(_ApplicationName, user.Username, user.Username, user.Email, string.Empty, user.Company, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
    
                return null;
            }
    
            public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
            {
                return GetUser(providerUserKey as string, userIsOnline);
            }
    
            public override string GetUserNameByEmail(string email) 
            { 
                var userRepository = new NewPixEditCMSApp.UserRepository();
    
                IList<User> users = userRepository.GetByEmail(email);
                if (users.Count > 0)
                    return users[0].Username;
                else
                    return null;
            } 
        }
    }
    

    Notice that the ApplicationName is set to "UmbracoMembershipProvider", which is necessary so that my provider "replaces" the Umbraco provider properly.

    Luckily, MyMembersRoleProvider still works fine inheriting from Umbraco.Web.Security.Providers.MembersRoleProvider.

  • Bendik Engebretsen 105 posts 202 karma points
    Feb 08, 2016 @ 11:28
    Bendik Engebretsen
    0

    Just thought I'd share an additional snag when using MS Access with PetaPoco: The DateTime data type.

    To map a Date/Time Field in the Access database to a poco object member, you should use a nullable DateTime, i.e. DateTime?. Also, Access is picky about its value, for instance you cannot use DateTime.Now, you will get an OleDbException when Update'ing. Instead, assign your DateTime member like this:

    new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0);
    

    Strange, I know, but then again, MS Access is a strange animal...

  • Norbert Haberl 32 posts 115 karma points
    Oct 30, 2017 @ 16:13
    Norbert Haberl
    0

    Hey, just would like to ask if it still works on Umbraco 7.5 and upwards? Thanks a lot for this code!

  • Bendik Engebretsen 105 posts 202 karma points
    May 02, 2018 @ 09:46
    Bendik Engebretsen
    0

    Hi Norbert and really sorry for answering so late.

    I've been busy building a new site for our company, and it is up and running now:-)

    And I am using basically the same MembershipProvider code as before, so this should work on Umbraco 7.7.7 which I am on now. I had to do som SQL adjustments because we have migrated from MS Access to MS SQL Server, but the principles are all the same.

    Just wanted to let you know if you are still working on this.

Please Sign in or register to post replies

Write your reply to:

Draft