Created
June 24, 2014 07:06
-
-
Save meziantou/0383d6003e9511241c0a to your computer and use it in GitHub Desktop.
ASP.NET Identity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class PasswordHasher : IPasswordHasher | |
{ | |
public int Pbkdf2IterCount { get; set; } | |
public int Pbkdf2SubkeyLength { get; set; } | |
public int SaltSize { get; set; } | |
public PasswordHasher() | |
{ | |
Pbkdf2IterCount = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".PBKDF2IterCount"], 50000); | |
Pbkdf2SubkeyLength = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".PBKDF2SubkeyLength"], 32); | |
SaltSize = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".SaltSize"], 16); | |
} | |
public string HashPassword(string password) | |
{ | |
if (password == null) | |
throw new ArgumentNullException("password"); | |
byte[] salt; | |
byte[] bytes; | |
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize, Pbkdf2IterCount)) | |
{ | |
salt = rfc2898DeriveBytes.Salt; | |
bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength); | |
} | |
byte[] inArray = new byte[4 + SaltSize + Pbkdf2SubkeyLength]; | |
Buffer.BlockCopy(BitConverter.GetBytes(Pbkdf2IterCount), 0, inArray, 0, 4); | |
Buffer.BlockCopy(salt, 0, inArray, 4, SaltSize); | |
Buffer.BlockCopy(bytes, 0, inArray, 4 + SaltSize, Pbkdf2SubkeyLength); | |
return Convert.ToBase64String(inArray); | |
} | |
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string password) | |
{ | |
if (hashedPassword == null) | |
return PasswordVerificationResult.Failed; | |
if (password == null) | |
throw new ArgumentNullException("password"); | |
byte[] numArray = Convert.FromBase64String(hashedPassword); | |
if (numArray.Length != (4 + SaltSize + Pbkdf2SubkeyLength)) | |
{ | |
return PasswordVerificationResult.Failed; | |
} | |
int iterations = BitConverter.ToInt32(numArray, 0); | |
byte[] salt = new byte[SaltSize]; | |
Buffer.BlockCopy(numArray, 4, salt, 0, SaltSize); | |
byte[] a = new byte[Pbkdf2SubkeyLength]; | |
Buffer.BlockCopy(numArray, 4 + SaltSize, a, 0, Pbkdf2SubkeyLength); | |
byte[] bytes; | |
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iterations)) | |
{ | |
bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength); | |
} | |
if (ByteArraysEqual(a, bytes)) | |
{ | |
if (iterations != Pbkdf2IterCount) | |
{ | |
return PasswordVerificationResult.SuccessRehashNeeded; | |
} | |
return PasswordVerificationResult.Success; | |
} | |
return PasswordVerificationResult.Failed; | |
} | |
private static bool ByteArraysEqual(byte[] a, byte[] b) | |
{ | |
if (ReferenceEquals(a, b)) | |
return true; | |
if (a == null || b == null || a.Length != b.Length) | |
return false; | |
bool flag = true; | |
for (int index = 0; index < a.Length; ++index) | |
flag = flag & (a[index] == b[index]); | |
return flag; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class PasswordValidator : IIdentityValidator<string> | |
{ | |
public PasswordValidator() | |
{ | |
MaximumPasswordLength = int.MaxValue; | |
// Most used password | |
InvalidPasswords = new HashSet<string>(); | |
InvalidPasswords.Add("000000"); | |
InvalidPasswords.Add("111111"); | |
InvalidPasswords.Add("123"); | |
InvalidPasswords.Add("1234"); | |
InvalidPasswords.Add("12345"); | |
InvalidPasswords.Add("123456"); | |
InvalidPasswords.Add("1234567"); | |
InvalidPasswords.Add("12345678"); | |
InvalidPasswords.Add("123456789"); | |
InvalidPasswords.Add("1234567890"); | |
InvalidPasswords.Add("0123456789"); | |
InvalidPasswords.Add("password"); | |
InvalidPasswords.Add("azerty"); | |
InvalidPasswords.Add("qwerty"); | |
InvalidPasswords.Add("abc123"); | |
InvalidPasswords.Add("iloveyou"); | |
InvalidPasswords.Add("adobe123"); | |
InvalidPasswords.Add("123123"); | |
InvalidPasswords.Add("admin"); | |
InvalidPasswords.Add("letmein"); | |
InvalidPasswords.Add("photoshop"); | |
InvalidPasswords.Add("monkey"); | |
InvalidPasswords.Add("shadow"); | |
InvalidPasswords.Add("sunshine"); | |
InvalidPasswords.Add("password1"); | |
InvalidPasswords.Add("princess"); | |
InvalidPasswords.Add("trustno1"); | |
} | |
public int MinimumRequiredDigits { get; set; } | |
public int MinimumRequiredPasswordLength { get; set; } | |
public int MaximumPasswordLength { get; set; } | |
public int MinimumRequiredNonAlphanumericCharacters { get; set; } | |
public int MinimumRequiredLowercases { get; set; } | |
public int MinimumRequiredUppercases { get; set; } | |
public int MinimumRequiredLettersOrDigits { get; set; } | |
public string PasswordStrengthRegularExpression { get; set; } | |
protected virtual HashSet<string> InvalidPasswords { get; set; } | |
/// <summary> | |
/// Returns true if the character is a digit between '0' and '9' | |
/// </summary> | |
/// <param name="c"></param> | |
/// <returns></returns> | |
protected virtual bool IsDigit(char c) | |
{ | |
if (c < '0') | |
{ | |
return false; | |
} | |
return c <= '9'; | |
} | |
/// <summary> | |
/// Returns true if the character is between 'a' and 'z' | |
/// </summary> | |
/// <param name="c"></param> | |
/// <returns></returns> | |
protected virtual bool IsLower(char c) | |
{ | |
if (c < 'a') | |
{ | |
return false; | |
} | |
return c <= 'z'; | |
} | |
/// <summary> | |
/// Returns true if the character is between 'A' and 'Z' | |
/// </summary> | |
/// <param name="c"></param> | |
/// <returns></returns> | |
protected virtual bool IsUpper(char c) | |
{ | |
if (c < 'A') | |
{ | |
return false; | |
} | |
return c <= 'Z'; | |
} | |
/// <summary> | |
/// Ensures that the string is of the required length and meets the configured requirements | |
/// </summary> | |
/// <param name="password"></param> | |
/// <returns></returns> | |
public virtual Task<IdentityResult> ValidateAsync([NotNull]string password) | |
{ | |
if (password == null) | |
throw new ArgumentNullException("password"); | |
List<string> errors = new List<string>(); | |
if (password.Length < MinimumRequiredPasswordLength) | |
{ | |
errors.Add(SR.FormatPasswordTooShort(MinimumRequiredPasswordLength)); | |
} | |
if (password.Length > MaximumPasswordLength) | |
{ | |
errors.Add(SR.FormatPasswordTooLong(MaximumPasswordLength)); | |
} | |
int digits = 0; | |
int lowers = 0; | |
int uppers = 0; | |
int nonAlphanum = 0; | |
foreach (var c in password) | |
{ | |
if (IsDigit(c)) | |
{ | |
digits++; | |
} | |
else if (IsLower(c)) | |
{ | |
lowers++; | |
} | |
else if (IsUpper(c)) | |
{ | |
uppers++; | |
} | |
else | |
{ | |
nonAlphanum++; | |
} | |
} | |
if (digits < MinimumRequiredDigits) | |
{ | |
if (MinimumRequiredDigits >= 2) | |
{ | |
errors.Add(SR.FormatPasswordRequireDigits(MinimumRequiredDigits)); | |
} | |
else | |
{ | |
errors.Add(SR.PasswordRequireDigit); | |
} | |
} | |
if (lowers < MinimumRequiredLowercases) | |
{ | |
if (MinimumRequiredLowercases >= 2) | |
{ | |
errors.Add(SR.FormatPasswordRequireLowers(MinimumRequiredLowercases)); | |
} | |
else | |
{ | |
errors.Add(SR.PasswordRequireLower); | |
} | |
} | |
if (uppers < MinimumRequiredUppercases) | |
{ | |
if (MinimumRequiredUppercases >= 2) | |
{ | |
errors.Add(SR.FormatPasswordRequireUppers(MinimumRequiredUppercases)); | |
} | |
else | |
{ | |
errors.Add(SR.PasswordRequireUpper); | |
} | |
} | |
if (nonAlphanum < MinimumRequiredNonAlphanumericCharacters) | |
{ | |
if (MinimumRequiredNonAlphanumericCharacters >= 2) | |
{ | |
errors.Add(SR.FormatPasswordRequireNonLettersOrDigits(MinimumRequiredNonAlphanumericCharacters)); | |
} | |
else | |
{ | |
errors.Add(SR.PasswordRequireNonLettersOrDigits); | |
} | |
} | |
if (uppers + lowers + digits < MinimumRequiredLettersOrDigits) | |
{ | |
if (MinimumRequiredLettersOrDigits >= 2) | |
{ | |
errors.Add(SR.FormatPasswordRequireLettersOrDigits(MinimumRequiredLettersOrDigits)); | |
} | |
else | |
{ | |
errors.Add(SR.PasswordRequireLettersOrDigit); | |
} | |
} | |
if (PasswordStrengthRegularExpression != null && Regex.IsMatch(password, PasswordStrengthRegularExpression)) | |
{ | |
errors.Add(SR.PasswordDoNotMatchRegex); | |
} | |
if (PasswordStrengthRegularExpression != null && Regex.IsMatch(password, PasswordStrengthRegularExpression)) | |
{ | |
errors.Add(SR.PasswordDoNotMatchRegex); | |
} | |
if (InvalidPasswords.Contains(password)) | |
{ | |
errors.Add(SR.PasswordIsInDictionary); | |
} | |
if (errors.Count == 0) | |
{ | |
return Task.FromResult(IdentityResult.Success); | |
} | |
string[] strArrays = { string.Join(" ", errors) }; | |
return Task.FromResult(IdentityResult.Failed(strArrays)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static UserManager Create(IdentityFactoryOptions<UserManager> options, IOwinContext context) | |
{ | |
UserManager userManager = new UserManager(); | |
userManager.PasswordHasher = new Security.PasswordHasher(); | |
userManager.PasswordValidator = new Security.PasswordValidator() | |
{ | |
MinimumRequiredPasswordLength = ConfigSection.MinimumPasswordLength, | |
MaximumPasswordLength = ConfigSection.MaximumPasswordLength, | |
MinimumRequiredNonAlphanumericCharacters = 0, | |
MinimumRequiredDigits = 0, | |
MinimumRequiredLowercases = 0, | |
MinimumRequiredUppercases = 0 | |
}; | |
userManager.UserLockoutEnabledByDefault = true; | |
userManager.MaxFailedAccessAttemptsBeforeLockout = 15; | |
userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); | |
userManager.EmailService = new EmailService(); | |
userManager.UserTokenProvider = new DataProtectorTokenProvider<User, int>(options.DataProtectionProvider.Create("ResetPassword", "VerifyEmail")); | |
userManager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<User, int> | |
{ | |
Subject = string.Format("{0} - {1}", SR.ProductName, SR.SecurityCodeSubject), | |
BodyFormat = SR.SecurityCode | |
}); | |
return userManager; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class UserManagerExtensions | |
{ | |
public static ValidateUserResult<TUser, TKey> ValidateUser<TUser, TKey>(this UserManager<TUser, TKey> userManager, string userName, string password, bool login) | |
where TUser : class, IUser<TKey> | |
where TKey : IEquatable<TKey> | |
{ | |
var user = userManager.FindByName(userName); | |
if (user == null) | |
{ | |
return new ValidateUserResult<TUser, TKey>(Resources.SR.InvalidCredential); | |
} | |
var result = userManager.CheckPassword(user, password); | |
if (result) | |
{ | |
bool isLockedOut = userManager.IsLockedOut(user.Id); | |
if (userManager.SupportsUserLockout && isLockedOut) | |
{ | |
DateTimeOffset lockoutEndDate = userManager.GetLockoutEndDate(user.Id); | |
return new ValidateUserResult<TUser, TKey>(Resources.SR.FormatUserLockedOutUntil(lockoutEndDate)); | |
} | |
userManager.ResetAccessFailedCount(user.Id); | |
return new ValidateUserResult<TUser, TKey>(user); | |
} | |
if (login && userManager.SupportsUserLockout) | |
{ | |
userManager.AccessFailed(user.Id); | |
} | |
return new ValidateUserResult<TUser, TKey>(Resources.SR.InvalidCredential); | |
} | |
} | |
public class ValidateUserResult<TUser, TKey> | |
where TUser : class, IUser<TKey> | |
{ | |
public ValidateUserResult([NotNull] TUser user) | |
{ | |
if (Equals(user, null)) throw new ArgumentNullException("user"); | |
User = user; | |
} | |
public ValidateUserResult(params string[] errors) | |
{ | |
if (errors == null || errors.Length == 0) | |
{ | |
errors = new[] { SR.DefaultError }; | |
} | |
Errors = errors; | |
} | |
public string[] Errors { get; set; } | |
public bool Succeeded | |
{ | |
get { return (Errors == null || Errors.Length == 0) && User != null; } | |
} | |
public TUser User { get; set; } | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment