Asp mvc 4 Membership and WebSecurity
Asked Answered
W

2

6

I need a suggestion about what to do. I'm currently using WebSecurity methods to do all the account related job. However it does not support E-mail uniqueness verification so I have a few options:

  1. Write (subclass) a new SimpleMembershipProvider overwriting the existing createuserAndAccount method to verify the email address. But i would also have to implement the login-logout features (just as websecurity does) and a few others.

  2. Add uniqueness constraints on the database and catch them on my code. However this would cause me to be DataBase dependent.

  3. This might be a little bit cheap, but i could copy/paste the WebSecurity source code (since its open) on a new class, and modify the createUserAndAccount method.

Any other option? I'm aiming for the option 3 at the moment, would be the fastest way. On a side note, on the future I will be requiring roles as well and I'm not sure if WebSecurity provides support for them.

Willard answered 15/4, 2013 at 16:48 Comment(1)
if it's a new database, another option would be to use the email as a username. the username is unique by default.Demarcusdemaria
R
5

If it were me, I'd probably go about it the following way:

First, assuming you're using SimpleMembership with Entity Framework or some database connection (ADO, LINQ to SQL, etc.) you're going to have two components: WebSecurity.* method calls, and the database connection to make profile changes. Personally, I'd add the CONSTRAINT to the database to ensure your data is pure, but you can also implement a membership service that handles this logic, too.

First, group these in to an interface that can be referenced in your controller (something like the following):

public interface IMembershipService
{
    Int32 CurrentUserId { get; }
    String CurrentUserName { get; }
    Boolean IsAuthenticated { get; }

    Boolean CreateUserAndAccount(String username, String password, String emailaddress = null);
    Boolean CreateUserAndAccount(String username, string password, out String confirmationToken, String emailaddress = null);
    Boolean Login(String username, String password, Boolean persistCookie = false);
    void Logout();
}

Then you can implement the service as a hybrid of SimpleMembership and your database connection. For the sake of keeping it generic, I use the IRepository<T> pattern, but this could be a direct DbContext, ObjectContext, etc. I'm also keeping it brief, so excuse the missing checksums and short implementation.

public class MembershipService : IMembershipService
{
    protected readonly SimpleMembershipProvider membershiProvider;
    protected readonly SimpleRoleProvider roleProvider;
    protected readonly IRepository<UserProfile> profileRepository;

    public MembershipService(IRepository<UserProfile> profileRepository)
    {
        this.membershipProvider = Membership.Provider as SimpleMembershipProvider;
        this.roleProvider = Role.Provider as SimpleRoleProvider;
        this.profileRepository = userRepository;
    }

    #region IMembershipService Implementation

    public Int32 CurrentUserId
    {
        get { return WebSecurity.CurrentUserId; }
    }
    public String CurrentUserName
    {
        get { return WebSecurity.CurrentUserName; }
    }
    public Boolean IsAuthenticated
    {
        get { return WebSecurity.IsAuthenticated; }
    }

    public Boolean CreateUserAndAccount(String username, String password, String emailaddress = null)
    {
        // validate the email address is unique
        if (!this.profileRepository.Any(x => x.EmailAddress == emailaddress))
        {
            WebSecurity.CreateUserAndAccount(username, password, new
            {
                EmailAddress = emailaddress
            }, createConfirmationToken);
            return true;
        }
        else
        {
            // handle the error how you see fit
            // (maybe even exception?)
            return false;
        }
    }
    public Boolean CreateUserAndAccount(String username, String password, out String confirmationToken, String emailaddress = null, out)
    {
        // validate the email address is unique
        if (this.profileRepository.First(x => x.EmailAddress == emailaddress) == null)
        {
            confirmationToken = WebSecurity.CreateUserAndAccount(username, password, new
            {
                EmailAddress = emailaddress
            }, createConfirmationToken);
            return true;
        }
        else
        {
            // handle the error how you see fit
            // (maybe even exception?)
            confirmationToken = String.Empty;
            return false;
        }
    }
    public Boolean Login(String username, String password, Boolean persistCookie = false)
    {
        return WebSecurity.Login(username, password, persistCookie);
    }
    public void Logout()
    {
        WebSecurity.Logout();
    }

    #endregion
}

Now you can reference this interface in your controller and have the logic in one place. if you're using a DI container, obviously register it, but here's an example implementation:

public class AccountController: Controller
{
    private readonly IMembershipService membershipService;

    public AccountController(IMembershipService membershipService)
    {
        this.membershipService = membershipService;
    }

    /* ... */

    [HttpPost, ValidateAntiForgeryToken]
    public ActionResult Register(LoginViewModel model, String returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (this.membershipService.CreateUserandAccount(model.Username, model.Password, model.EmailAddress))
            {
                this.membershipService.Login(model.Username, model.Password);
                if (!String.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
                {
                    return Redirect(returnUrl);
                }
                return RedirectToRoute("Default");
            }
            else
            {
                ModelState.AddModelError("", "Unable to register.");
            }
        }
        return View(model);
    }

    /* ... */
}

If you're using EntityFramework, you can also use the IValidatableObject. To resist duplicating, here's another SO question/answer that checks for a unique entry:

Entity Framework IValidatableObject

Recluse answered 16/4, 2013 at 13:41 Comment(1)
Loved it! This is a really nice solution.Willard
C
0

Use option one 1. do your custom membership inherit from ExtendedMemberShipprovider AccountMembershipProvider : ExtendedMembershipProvider

  1. User a wrapper for WebSecurity for Unit testablity Example

     public interface IWebSecurity
                    {
    
                        bool Login(string userName, string password, bool rememberMe = false);
                        bool ChangePassword(string userName, string currentnPassword, string newPassword);
                        void LogOut();
                    }
    

    public class WebSecurityWrapper : IWebSecurity

    {

        public bool Login(string userName, string password, bool rememberMe)
        {
            return WebSecurity.Login(userName, password, rememberMe);
        }
    
    
        public bool ChangePassword(string userName, string currentPassword, string newPassword)
        {
            return WebSecurity.ChangePassword(userName, currentPassword, newPassword);
        }
    
        public void LogOut()
        {
            WebSecurity.Logout();
        }
    }
    

Here you can add your email change whatever method you want

  1. Register your membership provider as the default provider in your web.config
Circuity answered 15/4, 2013 at 22:40 Comment(4)
I agree with creating an interface, but you can implement unique email constraint within the implementation easily. But I'm also a firm believer that this kind of constraint should be done at the database level and bubbled up to the web project.Recluse
Well, this a validation issue. I will validate is upfront and as you said also do validation the db side tooCircuity
Implementing ExtendedMembershipProvider would take too much work for just an extra functionality since i have to implement ALL the methods, while extending SimpleMembershipProvider i could just override the register method, wouldn't that be easy? I can add the login/logout methods in this new class as well. Why the need to implement the whole extendermembership?Willard
I extend ExtendedMembershipProvider and implement only what I needCircuity

© 2022 - 2024 — McMap. All rights reserved.