0

I'm working on a custom password validation that will do a bunch of extra checks, ideally including that the password the user is trying to create doesn't contain any permutations of their username.

We're using the Identity Framework, and my original intent was to just extend IIdentityValidator like this:

public class StrongPasswordValidator : IIdentityValidator<string>
{
    public int MinimumLength { get; set; }
    public int MaximumLength { get; set; }

    public void CustomPasswordValidator(int minimumLength, int maximumLength)
    {
        this.MinimumLength = minimumLength;
        this.MaximumLength = maximumLength;
    }

    public Task<IdentityResult> ValidateAsync(string item)
    {
        if (item.Length < MinimumLength)
        {
            return Task.FromResult(IdentityResult.Failed("Password must be a minimum of " + MinimumLength + " characters."));
        }
        if (item.Length > MaximumLength)
        {
            return Task.FromResult(IdentityResult.Failed("Password must be a maximum of " + MaximumLength + " characters."));
        }
        return Task.FromResult(IdentityResult.Success);
    }
}

But that can only take a string as an argument per the UserManager. Is there any way to pull in the username/a custom object here as well, or otherwise compare the password string to the username before persisting the information?

EDIT

For context, this is how the password validator is set and used.

UserManager:

public CustomUserManager(IUserStore<User> store,
        IPasswordHasher passwordHasher,
        IIdentityMessageService emailService)
        : base(store)
    {
        //Other validators
        PasswordValidator = new StrongPasswordValidator
        {
            MinimumLength = 8,
            MaximumLength = 100
        };
        PasswordHasher = passwordHasher;
        EmailService = emailService;
    }

User creation:

var userManager = new CustomUserManager(userStore,
                new BCryptHasher(), emailService.Object);    

userManager.CreateAsync(user, Password);
RSid
  • 768
  • 1
  • 8
  • 30
  • Why can't you use `public class StrongPasswordValidator : IIdentityValidator where TUser : class, Microsoft.AspNet.Identity.IUser` and `UserManager.UserValidator = new StrongPasswordValidator(UserManager);` – artm Oct 22 '14 at 13:14
  • The IUser class only gives me the username parameter, while I need access to both the username and password strings in the validator. – RSid Oct 22 '14 at 14:15

2 Answers2

1

Instead of using the PasswordValidator use the UserValidator which will allow you to use your user object as the argument. The type for ValidateAsync comes from the generic parameter.

public class MyUserValidator : IIdentityValidator<User>
{
    public Task<IdentityResult> ValidateAsync(User item)
    {
        ...
    }
}

In your usermanager register it...

public ApplicationUserManager(IUserStore<User> store)
        : base(store)
{
    UserValidator = new MyUserValidator<User>(...);
}
jamesSampica
  • 12,230
  • 3
  • 63
  • 85
  • Looking at UserManager, IIdentityValidator is hard-coded to just take a string or TUser, so I don't think this will work for the same reason as @artm's suggestion doesn't. – RSid Oct 22 '14 at 14:19
  • Have you tried using the `UserValidator` instead of `PasswordValidator`? – jamesSampica Oct 22 '14 at 15:18
  • Yup - the problem there is that UserValidator only takes the User object, which in turn inherits from IdentityUser, which only provides a username (no password). – RSid Oct 22 '14 at 17:14
  • IdentityUser isn't part of Identity, so that's something you can override. – jamesSampica Oct 22 '14 at 17:52
  • Ah, that would do it! One question though; wouldn't the fields that you add to the User model need to reflect the DB fields backing it (so be the password hash, not the password string we're trying to validate)? – RSid Oct 23 '14 at 19:01
  • Presumably you would validate it before it gets hashed. You could just stick it into the hashed field as a temporary location until it passes validation. If it passes, just replace it with the hashed value. – jamesSampica Oct 25 '14 at 23:39
0

Just add one/many field(s) of the type you wish in StrongPasswordValidator, and inject the values in constructor

public class StrongPasswordValidator : IIdentityValidator<string>
{
    public int MinimumLength { get; set; }
    public int MaximumLength { get; set; }
    private readonly MyCustomObjectType _myCustomObject;

    public void CustomPasswordValidator(int minimumLength, int maximumLength, 
                                        MyCustomObjectType myCustomObject)
    {
        // guard clause
        if(myCustomObject == null)
        {
           throw new ArgumentExpcetion("myCustomObject was not provided.");
        }
        _myCustomObject = myCustomObject;
        this.MinimumLength = minimumLength;
        this.MaximumLength = maximumLength;
    }

    public Task<IdentityResult> ValidateAsync(string item)
    {
        // use _myCustomObject here
        if (item.Length < MinimumLength)
        {
            return Task.FromResult(IdentityResult.Failed("Password must be a minimum of " + MinimumLength + " characters."));
        }
        if (item.Length > MaximumLength)
        {
            return Task.FromResult(IdentityResult.Failed("Password must be a maximum of " + MaximumLength + " characters."));
        }
        return Task.FromResult(IdentityResult.Success);
    }
}

Now, when you will create instance of StrongPasswordValidator, you can inject your custom object.

MyCustomObjectType myCustomObject = new MyCustomObjectType();
UserManager.PasswordValidator = new StrongPasswordValidator(6, 10, myCustomObject);

EDIT

Based on updated post, what stops you from newing up StrongPasswordValidator by passing to its constructor the object you need.

public CustomUserManager(IUserStore<User> store,
        IPasswordHasher passwordHasher,
        IIdentityMessageService emailService)
        : base(store)
    {
        //Other validators
        MyCustomObjectType myCustomObject = new MyCustomObjectType();
        PasswordValidator = new StrongPasswordValidator(8, 100, myCustomObject);
        PasswordHasher = passwordHasher;
        EmailService = emailService;
    }

And then create a user.

var userManager = new CustomUserManager(userStore,
                new BCryptHasher(), emailService.Object);    

userManager.CreateAsync(user, Password);
Michael
  • 2,961
  • 2
  • 28
  • 54
  • That was my initial instinct too, but the problem is that we don't create instances of the StrongPasswordValidator like that; instead we set the conditions for the StrongPasswordValidator within the UserManager and create instances of that, then call CreateAsync on it and all the validators within the manager automatically process the information that's passed in – RSid Oct 22 '14 at 14:23
  • @RSid can you post that piece also? – Michael Oct 22 '14 at 14:30
  • 1
    How would `myCustomObject` contain the relevant data to validate against such as a username? You would have to be able to get the current request data and pass that into the UserManager. – jamesSampica Oct 22 '14 at 16:07
  • @Shoe good point, now I follow, didn't realize `myCustomObject` should contain `username`. – Michael Oct 22 '14 at 16:41