0

I'm trying to build a web app using as much of the out-of-the-box ASP.NET security as I can. I got it up and running pretty easily using the default patterns but now I'm trying to make a simple modification and am getting all sorts of confused. Basically, all I'm trying to do is use a generic userID (a stringified GUID, for instance) instead of a username as the primary key for the user account. Googling yields many threads on how to inherit from MembershipProvider but they all assuming you're overriding the same method signatures like ::CreateUser(string username, string password, ...). I'm kind of lost because I don't know what event is raised that causes ::CreateUser() to be called, nor what method/class to override in order to change how it's called.

Yes, I know I can "pretend" username is userID in this particular scenario but I'd really like to understand how this works in case I want to make addition mods in the future.

Also, I'm trying to customize what info is stored in the authorization cookie but I think I've got a handle on that. Some of the relevant code in its current half-baked condition is shown below. Thanks.

MyMembershipProvider.cs

namespace MyOrg.MyApp.Infrastructure
{
    public class MyAppMembershipProvider : MembershipProvider
    {
        int minPasswordLength = MyOrg.MyApp.Common.Config.minPasswordLength;

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            /* WANT TO SWITCH THIS OVER SO IT USES userID INSTEAD OF username */

            User user = null;
            using (DB db = new DB())
            {
                DateTime creationDate = DateTime.UtcNow;

                user = new User() {
                    username = username,
                    email = email,
                    pwd = MyOrg.Utilities.Misc.PasswordHash.CreateHash(password)
                };

                int recAdded = db.insertUser(user);

                if (recAdded > 0)
                {
                    status = MembershipCreateStatus.Success;
                }
                else
                {
                    status = MembershipCreateStatus.UserRejected;
                }
            }

            return getMembershipUserFromUser(user);
        }
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            /* WANT TO SWITCH THIS OVER SO IT USES userID INSTEAD OF username */

            User user = null;
            using (DB db = new DB())
            {
                user = db.getUser(username);
            }
            return getMembershipUserFromUser(user);
        }
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            /* WANT TO SWITCH THIS OVER SO IT USES userID INSTEAD OF username */
            User user = null;
            using (DB db = new DB())
                user = db.getUser(username);

            bool result = PasswordHash.ValidatePassword(oldPassword, user.pwd);
            if (result)
            {
                user.pwd = Utilities.Misc.PasswordHash.CreateHash(newPassword);
                user.lastPwdChange = DateTime.UtcNow;
                using (DB db = new DB())
                    db.updateUser(user);
            }
            return result;
        }

        public static bool ValidateUser(string username, string password, out string userID)
        {
            /* WANT TO SWITCH THIS OVER SO IT USES userID INSTEAD OF username */

            userID = null;
            using (DB db = new DB())
            {
                User user = db.getUserByUsername(username);
                if (user == null)
                    return false;

                bool result = MyOrg.Utilities.Misc.PasswordHash.ValidatePassword(password, user.pwd);

                if (result)
                {
                    user.failedPwdAttemptCount = 0;
                    user.failedPwdAnswerAttemptCount = 0;
                    user.lastLogin = DateTime.UtcNow;
                    userID = user.id;
                }
                else
                {
                    user.failedPwdAttemptCount++;
                    user.failedPwdAttemptWindowStart = DateTime.UtcNow;
                }
                db.updateUser(user);
                return result;
            }
        }
        private MembershipUser getMembershipUserFromUser(User user)
        {
            return new MembershipUser(
                "MyAppMembershipProvider",
                user.username,
                "dunno",
                user.email,
                user.pwdQuestion,
                user.comment,
                user.isApproved,
                user.isLockedOut,
                user.created,
                user.lastLogin,
                user.lastActivity,
                user.lastPwdChange,
                user.lastLockedOut);
        }
        public override int MinRequiredPasswordLength
        {
            get { return minPasswordLength; }
        }

        /* Other inherited methods not implemented*/


    }
}

AccountController.cs

namespace MyOrg.MyApp.Controllers
{
    public class AccountController : BaseController
    {

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

        [HttpPost]
        public ActionResult LogOn(FormCollection form)
        {
            string username = form["username"];
            string password = form["password"];
            bool rememberMe = form["rememberMe"] != null;
            string returnUrl = form["returnUrl"];

            string userID;
            if (MyAppMembershipProvider.ValidateUser(username, password, out userID))
            {
                CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
                serializeModel.userID = userID;
                serializeModel.username = username;

                System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();

                string userData = serializer.Serialize(serializeModel);

                FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                  1,
                  userID,
                  DateTime.Now,
                  DateTime.Now.AddMinutes(15),
                  false,
                  userData);

                string encTicket = FormsAuthentication.Encrypt(authTicket);
                HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
                Response.Cookies.Add(faCookie);

                return RedirectToAction("Index", "Home");
            }
            else
            {
                ViewBag.validationMsg = "The user name or password provided is incorrect.";
            }

            // If we got this far, something failed, redisplay form
            ViewBag.username = username;
            return View();
        }

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

        public ActionResult Details()
        {
            RegisterModel model = new RegisterModel();

            User user;
            UserProfile profile;
            using (DB db = new DB())
            {
                user = db.getUser(User.userID);
                profile = db.getUserProfile(User.userID);
            }

            model.UserName = user.username;
            model.Email = user.email;
            model.Zip = profile.get(UserProfile.Properties.zip);

            return View(model);
        }

        public ActionResult Edit()
        {
            RegisterModel model = new RegisterModel();

            model = null;


            User user;
            UserProfile profile;
            using (DB db = new DB())
            {
                user = db.getUser(User.userID);
                profile = db.getUserProfile(User.userID);
            }

            model.UserName = user.username;
            model.Email = user.email;
            model.Zip = profile.get(UserProfile.Properties.zip);

            return View(model);
        }

        [HttpPost]
        public ActionResult Edit(RegisterModel model)
        {
            UserProfile profile = new UserProfile();
            profile.userID = model.UserName;
            profile.set(UserProfile.Properties.zip, model.Zip);

            using (DB db = new DB())
            {
                User user = db.getUser(model.UserName);
                user.email = model.Email;
                db.updateUser(user);

                db.updateUserProfile(profile);
            }

            return RedirectToAction("Index", "Home");
        }

        public ActionResult Register()
        {
            RegisterModel m = new RegisterModel();
            return View(m);
        }

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            using (DB db = new DB())
            {
                // Check unique username
                if (db.getUser(model.UserName) != null)
                {
                    ViewBag.validationMsg = String.Format("The username '{0}' is already taken. Please choose another.", model.UserName);
                    return View();
                }

                Membership.CreateUser(model.UserName, model.Password, model.Email);

                UserProfile profile = new UserProfile();
                profile.userID = model.UserName;
                profile.set(UserProfile.Properties.zip, model.Zip);
                db.insertUserProfile(profile);

                string confirmationCode = Guid.NewGuid().ToString();
                db.insertConfirmationCode(model.UserName, confirmationCode);

                // Send Email w/ confirmation code
                Infrastructure.AccountConfirmationEmail email = new Infrastructure.AccountConfirmationEmail(
                    model.Email,
                    model.UserName,
                    confirmationCode
                );
                email.send();
            }

            return View("ConfirmationEmailSent");
        }

        public ActionResult Confirm(string username, string code)
        {
            ViewBag.username = username;
            ViewBag.code = code;
            return View();
        }

        [HttpPost]
        public ActionResult Confirm(string username, string code, string password)
        {
            string storedCode = null;
            using (DB db = new DB())
                storedCode = db.getConfirmationCode(username);

            if (storedCode == null)
            {
                // Username not found in confirmation_codes
                return View("ConfirmationCodeNotFound");
            }
            else
            {
                if (code == storedCode)
                {
                    if (Membership.ValidateUser(username, password))
                    {
                        using (DB db = new DB())
                        {
                            User user = db.getUser(username);
                            user.isConfirmed = true;
                            db.updateUser(user);

                            db.deleteConfirmationCode(username);

                            FormsAuthentication.SetAuthCookie(username, false); 
                            return RedirectToAction("Index", "Home");
                        }
                    }
                    else
                    {
                        ViewBag.username = username;
                        ViewBag.code = code;
                        ViewBag.validationMsg = "Incorrect Password";
                        return View();
                    }
                }
                else
                {
                    return View("ConfirmationCodeNotFound");
                }
            }
        }

        [Authorize]
        public ActionResult ChangePassword()
        {
            return View();
        }

        [Authorize]
        [HttpPost]
        public ActionResult ChangePassword(string OldPassword, string NewPassword)
        {
            bool changePasswordSucceeded;
            try
            {
                MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
                changePasswordSucceeded = currentUser.ChangePassword(OldPassword, NewPassword);
            }
            catch (Exception)
            {
                changePasswordSucceeded = false;
            }

            if (changePasswordSucceeded)
            {
                return RedirectToAction("ChangePasswordSuccess");
            }
            else
            {
                ViewBag.validationMsg = "The old password is incorrect.";
                ViewBag.newPassword = NewPassword;
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

        [Authorize]
        public ActionResult ChangePasswordSuccess()
        {
            return View();
        }



    }
}

Global.asax.cs

namespace MyOrg.MyApp
{

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            //filters.Add(new HandleErrorAttribute());
            filters.Add(new Infrastructure.HandleExceptionsAttribute()); // Implementing custom exception handling
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

        }

        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie != null)
            {
                FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
                string userID = ticket.Name;
                CustomPrincipal principal = new CustomPrincipal(userID);
                HttpContext.Current.User = principal;
            }
        }

        protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
        {
            HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

            if (authCookie != null)
            {
                FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);

                JavaScriptSerializer serializer = new JavaScriptSerializer();

                CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);

                CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
                newUser.userID = serializeModel.userID;
                newUser.username = serializeModel.username;

                HttpContext.Current.User = newUser;
            }
        }
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

Web.config

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="***" connectionString="***" />
  </connectionStrings>

  <appSettings>
    <add key="webpages:Version" value="1.0.0.0"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>

  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>

    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>

    <machineKey
      validationKey="***"
      decryptionKey="***"
      validation="SHA1" />

    <membership defaultProvider="MyAppMembershipProvider" userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <add
          name="MyAppMembershipProvider"
          type="MyOrg.MyApp.Infrastructure.MyAppMembershipProvider"
          connectionStringName="dummy"
          enablePasswordReset="true"
          requiresQuestionAndAnswer="true"
          writeExceptionsToEventLog="true" />
      </providers>
    </membership>

    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
      </providers>
    </roleManager>

    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages"/>
      </namespaces>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

</configuration>
Gadzooks34
  • 1,718
  • 2
  • 20
  • 29

1 Answers1

0
public override MembershipUser CreateUser(string username, ...)
{
   /* WANT TO SWITCH THIS OVER SO IT USES userID INSTEAD OF username */
}

Unfortunately, you cannot modify Membership Provier methods' parameters.

Here are the basic concept -

Step 1

When a user enters username and password at Login Control, Membership Provide calls ValidateUser. Then create authentication ticket and save username in the encrypted cookie.

Step 2

When the user requests the sub sequence pages, Membership Provider retrieves username from the cookie, and calls GetUser.

Win
  • 61,100
  • 13
  • 102
  • 181
  • OK. So it sounds like the only option is completely bypassing the MembershipUser object. I'm assuming that, in that case, I'm just moving completely away from the built-in security model? – Gadzooks34 Mar 24 '14 at 16:32
  • Based on your requirement, you are modifying a lot of Membership Provider's features. If it is the case, you might not want to use Membership Provider. Instead, you can create **FormAuthentication** ticket and **IPrincipal** object by yourself. [Here](http://stackoverflow.com/a/18108403/296861) is an example. – Win Mar 24 '14 at 18:44