Beispiel #1
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a user name, and password.
 /// </summary>
 /// <param name="name">The unique user name.</param>
 /// <param name="password">The matching password.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="duration">The amount of time that the issued token will be valid.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user instance.  If the user did not exist or the
 /// credentials are incorrect then the IsAuthenticated flag
 /// will be false.  If the credentials were correct the
 /// IsAuthenticated flag will be true.
 /// </returns>
 public override UserIdentity AuthenticateUser(
     string name, string password, UserSessionDurationType duration,
     string ipAddress, ExecutionResults result)
 {
     return(AuthenticateUser(name: name, password: password, duration: duration,
                             ipAddress: ipAddress, checkHistory: true, allowUpdateHash: true, result: result));
 }
        /// <summary>
        /// Registers a new user.  The PasswordHash property should be the actual password.
        /// </summary>
        /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
        /// <param name="duration">The amount of time that the initial session will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>A boolean indicating success (true) or failure (false).</returns>
        public virtual UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            var password = user.PasswordHash; //grab before it gets hashed.

            if (!RegisterBase(user, result))
            {
                return(new UserIdentity());
            }

            return(AuthenticateUser(name: user.Name, password: password, duration: duration,
                                    ipAddress: ipAddress, checkHistory: false, allowUpdateHash: false, result: result));
        }
Beispiel #3
0
        /// <summary>
        /// Authenticates against the data store and returns a UserIdentity given
        /// a token returned from a previous authentication.
        /// </summary>
        /// <param name="token">The unique token.</param>
        /// <param name="duration">The amount of time that the renewed token will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>
        /// A valid user identity instance.  If the token is incorrect or expired
        /// then the IsAuthenticated flag will be false.  Otherwise the identity
        /// will be authenticated.
        /// </returns>
        public virtual UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            const string errorMsg = "Authentication token invalid.";
            Guid         renewalToken;

            if (!Guid.TryParse(token, out renewalToken))
            {
                result.AppendError(errorMsg);
                return(new UserIdentity());
            }
            var session = GetUserSession(renewalToken);

            if (session == null)
            {
                result.AppendError(errorMsg);
                return(new UserIdentity());
            }
            if (session.ExpirationDate < DateTime.UtcNow)
            {
                result.AppendError(errorMsg);
                return(new UserIdentity());
            }

            var history = GetSessionAuthenticationHistory(session);

            if (history == null)
            {
                result.AppendError(errorMsg);
                return(new UserIdentity());
            }
            if (history.IPAddress != ipAddress && !DisableIpAddressSessionLock)
            {                               //coming from a new IPAddress, token was stolen or user is coming from a new dynamic IP address (new internet connection?)
                result.AppendError(errorMsg);
                return(new UserIdentity()); //force new login with password (essentially approves this new IP address)
                //WARN: This is a weak valid check.  An imposter can fake the source IP, and a legitimate user could hop IP Addresses during a single session (mobile device)
            }

            if ((DateTime.UtcNow - session.RenewedDate).Duration() > TimeSpan.FromMinutes(1))
            {   //reduce the number of writes.  Only need to know within a minute how many active users there are.  There may be many json requests for a single page in a minute.
                session.RenewedDate    = DateTime.UtcNow;
                session.ExpirationDate = DateTime.UtcNow.AddMinutes(duration == UserSessionDurationType.PublicComputer ? PublicSessionDuration : ExtendedSessionDuration);
                SaveUserSession(session);
            }
            history.UserSession = session;
            return(new UserIdentity(history, Name));
        }
Beispiel #4
0
        /// <summary>
        /// Registers a new user.  The PasswordHash property should be the actual password.
        /// </summary>
        /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
        /// <param name="duration">The amount of time that the initial session will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>A boolean indicating success (true) or failure (false).</returns>
        public override UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            string password = user.PasswordHash;

            if (!ValidateName(user.Name, result) || !ValidatePassword(password, result))
            {
                return(new cs.UserIdentity());
            }

            var existing = GetUserByName(user.Name);

            if (existing != null)
            {   //seed user table with deleted users with names you don't want users to have
                result.AppendError("The name you specified cannot be used.");
                return(new cs.UserIdentity());
            }
            if (user.UserID.Equals(Guid.Empty))
            {
                user.UserID = Guid.NewGuid();
            }

            HashProvider hasher = HashManager.SelectProvider();
            var          salt   = new UserSalt
            {
                PasswordSalt = hasher.GetSalt(),
                UserID       = user.UserID,
                HashGroup    = new Random(DateTime.Now.Second).Next(HashGroupMinimum, HashGroupMaximum),
                HashName     = hasher.Name
            };

            user.PasswordHash = hasher.Hash(salt.PasswordSalt, password,
                                            salt.HashGroup + BaseHashIterations);
            using (var scope = new System.Transactions.TransactionScope())
            {
                //starts as a lightweight transaction
                SaveUser(user);
                //enlists in a full distributed transaction if users and salts have different connection strings
                SaveUserSalt(salt);
            }
            return(AuthenticateUser(name: user.Name, password: password, duration: duration,
                                    ipAddress: ipAddress, checkHistory: false, allowUpdateHash: false, result: result));
        }
Beispiel #5
0
        /// <summary>
        /// Authenticates against the data store and returns a UserIdentity given
        /// a token returned from a previous authentication.
        /// </summary>
        /// <param name="token">The unique token.</param>
        /// <param name="duration">The amount of time that the renewed token will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>
        /// A valid user identity instance.  If the token is incorrect or expired
        /// then the IsAuthenticated flag will be false.  Otherwise the identity
        /// will be authenticated.
        /// </returns>
        public override UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            String errorMsg = "Authentication token invalid.";
            Guid   renewalToken;

            if (!Guid.TryParse(token, out renewalToken))
            {
                result.AppendError(errorMsg);
                return(new cs.UserIdentity());
            }
            UserSession session = GetUserSession(renewalToken);

            if (session == null)
            {
                result.AppendError(errorMsg);
                return(new cs.UserIdentity());
            }
            AuthenticationHistory history = GetSessionAuthenticationHistory(session);

            if (history == null)
            {
                result.AppendError(errorMsg);
                return(new cs.UserIdentity());
            }
            else if (history.IPAddress != ipAddress)
            {                                  //coming from a new IPAddress, token was stolen or user is coming from a new dynamic IP address (new internet connection?)
                result.AppendError(errorMsg);
                return(new cs.UserIdentity()); //force new login with password (essentially approves this new IP address)
                //WARN: is this a valid check?  Can an imposter just fake the source IP?  Could a legitimate user hop IP Addresses during a single session?
            }

            session.RenewedDate    = DateTime.UtcNow;
            session.ExpirationDate = DateTime.UtcNow.AddMinutes(duration == UserSessionDurationType.PublicComputer ? PublicSessionDuration : ExtendedSessionDuration);
            SaveUserSession(session);
            history.UserSession = session;
            return(new UserIdentity(history, this.Name));
        }
Beispiel #6
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a user name, and password.
 /// </summary>
 /// <param name="name">The unique user name.</param>
 /// <param name="password">The matching password.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="duration">The amount of time that the issued token will be valid.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user instance.  If the user did not exist or the
 /// credentials are incorrect then the IsAuthenticated flag
 /// will be false.  If the credentials were correct the
 /// IsAuthenticated flag will be true.
 /// </returns>
 public static UserIdentity AuthenticateUser(string name, string password, UserSessionDurationType duration, string ipAddress,
                                             ExecutionResults result)
 {
     return(Provider.AuthenticateUser(name, password, duration, ipAddress, result));
 }
Beispiel #7
0
        /// <summary>
        /// Authenticates a user with the requested rule options.  This internal method is called
        /// by the other public versions of the method.  Override in a derived class if you want
        /// to change the rule interpretations or add new rules.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="password"></param>
        /// <param name="duration"></param>
        /// <param name="ipAddress"></param>
        /// <param name="checkHistory"></param>
        /// <param name="allowUpdateHash"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        protected virtual UserIdentity AuthenticateUser(string name, string password,
            UserSessionDurationType duration, string ipAddress, bool checkHistory,
            bool allowUpdateHash, ExecutionResults result)
        {
            if (checkHistory)
            {
                var recentFailures = GetRecentFailedUserNameAuthenticationCount(name);
                if (recentFailures > AllowedFailuresPerPeriod)
                    return FailAuthenticateUser(name, ipAddress, result);
            }
            var user = GetUserByName(name);
            if (user == null)
                return FailAuthenticateUser(name, ipAddress, result);
            var salt = GetUserSalt(user.UserID);
            if (salt == null)
                return FailAuthenticateUser(name, ipAddress, result);

            //this should get a named hashProvider used to originally hash the password...
            //  fallback to 'default' provider in legacy case when we didn't store the name.
            var hasher = !string.IsNullOrEmpty(salt.HashName) ? HashManager.Providers[salt.HashName] : HashManager.DefaultProvider;
            var passwordHash = hasher.Hash(salt.PasswordSalt, password, salt.HashGroup + BaseHashIterations);
            if (user.PasswordHash != passwordHash)
                return FailAuthenticateUser(name, ipAddress, result);
            var session = new UserSession
            {
                CreatedDate = DateTime.UtcNow,
                ExpirationDate = DateTime.UtcNow.AddMinutes(duration == UserSessionDurationType.PublicComputer ? PublicSessionDuration : ExtendedSessionDuration),
                UserID = user.UserID,
                RenewalToken = Guid.NewGuid()
            };
            var history = new AuthenticationHistory
            {
                CreatedDate = session.CreatedDate,
                IPAddress = ipAddress,
                IsAuthenticated = true,
                UserName = name,
                SessionID = session.SessionID,
                UserSession = session
            };
            using (var scope = new System.Transactions.TransactionScope())
            {
                if (allowUpdateHash && (hasher.IsObsolete || user.PasswordHashUpdatedDate < DateTime.UtcNow.AddMonths(-1)))
                {
                    //update hashes on regular basis, keeps the iterations in latest range for current users, and with a 'current' hash provider.
                    hasher = HashManager.SelectProvider();
                    salt.PasswordSalt = hasher.GetSalt();
                    salt.HashGroup = new Random(DateTime.Now.Second).Next(HashGroupMinimum, HashGroupMaximum);
                    salt.HashName = hasher.Name;
                    user.PasswordHash = hasher.Hash(salt.PasswordSalt, password, salt.HashGroup + BaseHashIterations);
                    user.PasswordHashUpdatedDate = DateTime.UtcNow;
                    //starts as a lightweight transaction
                    SaveUser(user);
                    //enlists in a full distributed transaction if users and salts have different connection strings
                    SaveUserSalt(salt);
                }
                //either continues distributed transaction if applicable,
                //  or creates a new lightweight transaction for these two commands
                SaveUserSession(session);
                InsertUserHistory(history);
                scope.Complete();
            }
            return new UserIdentity(history, Name);
        }
Beispiel #8
0
        /// <summary>
        /// Registers a new user.  The PasswordHash property should be the actual password.
        /// </summary>
        /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
        /// <param name="duration">The amount of time that the initial session will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>A boolean indicating success (true) or failure (false).</returns>
        public virtual UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            var password = user.PasswordHash; //grab before it gets hashed.
            if (!RegisterBase(user, result))
                return new UserIdentity();

            return AuthenticateUser(name: user.Name, password: password, duration: duration,
                                    ipAddress: ipAddress, checkHistory: false, allowUpdateHash: false, result: result);
        }
Beispiel #9
0
        /// <summary>
        /// Authenticates against the data store and returns a UserIdentity given
        /// a token returned from a previous authentication.
        /// </summary>
        /// <param name="token">The unique token.</param>
        /// <param name="duration">The amount of time that the renewed token will be valid.</param>
        /// <param name="ipAddress">The internet address where the user is connecting from.</param>
        /// <param name="result">A ExecutionResults instance to add applicable
        /// warning and error messages to.</param>
        /// <returns>
        /// A valid user identity instance.  If the token is incorrect or expired
        /// then the IsAuthenticated flag will be false.  Otherwise the identity
        /// will be authenticated.
        /// </returns>
        public virtual UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
        {
            const string errorMsg = "Authentication token invalid.";
            Guid renewalToken;
            if (!Guid.TryParse(token, out renewalToken))
            {
                result.AppendError(errorMsg);
                return new UserIdentity();
            }
            var session = GetUserSession(renewalToken);
            if (session == null)
            {
                result.AppendError(errorMsg);
                return new UserIdentity();
            }
            var history = GetSessionAuthenticationHistory(session);
            if (history == null)
            {
                result.AppendError(errorMsg);
                return new UserIdentity();
            }
            if (history.IPAddress != ipAddress)
            {	//coming from a new IPAddress, token was stolen or user is coming from a new dynamic IP address (new internet connection?)
                result.AppendError(errorMsg);
                return new UserIdentity(); //force new login with password (essentially approves this new IP address)
                //WARN: is this a valid check?  Can an imposter just fake the source IP?  Could a legitimate user hop IP Addresses during a single session?
            }

            if ((DateTime.UtcNow - session.RenewedDate).Duration() > TimeSpan.FromMinutes(1))
            {   //reduce the number of writes.  Only need to know within a minute how many active users there are.  There may be many json requests for a single page in a minute.
                session.RenewedDate = DateTime.UtcNow;
                session.ExpirationDate = DateTime.UtcNow.AddMinutes(duration == UserSessionDurationType.PublicComputer ? PublicSessionDuration : ExtendedSessionDuration);
                SaveUserSession(session);
            }
            history.UserSession = session;
            return new UserIdentity(history, Name);
        }
Beispiel #10
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given 
 /// a user name, and password.
 /// </summary>
 /// <param name="name">The unique user name.</param>
 /// <param name="password">The matching password.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="duration">The amount of time that the issued token will be valid.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user instance.  If the user did not exist or the 
 /// credentials are incorrect then the IsAuthenticated flag
 /// will be false.  If the credentials were correct the 
 /// IsAuthenticated flag will be true.
 /// </returns>
 public virtual UserIdentity AuthenticateUser(
     string name, string password, UserSessionDurationType duration,
     string ipAddress, ExecutionResults result)
 {
     return AuthenticateUser(name: name, password: password, duration: duration,
         ipAddress: ipAddress, checkHistory: true, allowUpdateHash: true, result: result);
 }
Beispiel #11
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a token returned from a previous authentication.
 /// </summary>
 /// <param name="token">The unique token.</param>
 /// <param name="duration">The amount of time that the renewed token will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user identity instance.  If the token is incorrect or expired
 /// then the IsAuthenticated flag will be false.  Otherwise the identity
 /// will be authenticated.
 /// </returns>
 public static UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
 {
     return(Provider.AuthenticateUser(token, duration, ipAddress, result));
 }
Beispiel #12
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a user name, and password.
 /// </summary>
 /// <param name="name">The unique user name.</param>
 /// <param name="password">The matching password.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="duration">The amount of time that the issued token will be valid.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user instance.  If the user did not exist or the
 /// credentials are incorrect then the IsAuthenticated flag
 /// will be false.  If the credentials were correct the
 /// IsAuthenticated flag will be true.
 /// </returns>
 public abstract UserIdentity AuthenticateUser(string name, string password, UserSessionDurationType duration, string ipAddress,
                                               ExecutionResults result);
 /// <summary>
 /// Registers a new user.  The PasswordHash property should be the actual password.
 /// </summary>
 /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
 /// <param name="duration">The amount of time that the initial session will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>A boolean indicating success (true) or failure (false).</returns>
 public static UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
 {
     return Provider.RegisterUser(user, duration, ipAddress, result);
 }
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a token returned from a previous authentication.
 /// </summary>
 /// <param name="token">The unique token.</param>
 /// <param name="duration">The amount of time that the renewed token will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user identity instance.  If the token is incorrect or expired
 /// then the IsAuthenticated flag will be false.  Otherwise the identity
 /// will be authenticated.
 /// </returns>
 public static UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
 {
     return Provider.AuthenticateUser(token, duration, ipAddress, result);
 }
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given 
 /// a user name, and password.
 /// </summary>
 /// <param name="name">The unique user name.</param>
 /// <param name="password">The matching password.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="duration">The amount of time that the issued token will be valid.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user instance.  If the user did not exist or the 
 /// credentials are incorrect then the IsAuthenticated flag
 /// will be false.  If the credentials were correct the 
 /// IsAuthenticated flag will be true.
 /// </returns>
 public static UserIdentity AuthenticateUser(string name, string password, UserSessionDurationType duration, string ipAddress,
                                               ExecutionResults result)
 {
     return Provider.AuthenticateUser(name, password, duration, ipAddress, result);
 }
Beispiel #16
0
 /// <summary>
 /// Registers a new user.  The PasswordHash property should be the actual password.
 /// </summary>
 /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
 /// <param name="duration">The amount of time that the initial session will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>A boolean indicating success (true) or failure (false).</returns>
 public abstract UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result);
Beispiel #17
0
 /// <summary>
 /// Authenticates against the data store and returns a UserIdentity given
 /// a token returned from a previous authentication.
 /// </summary>
 /// <param name="token">The unique token.</param>
 /// <param name="duration">The amount of time that the renewed token will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>
 /// A valid user identity instance.  If the token is incorrect or expired
 /// then the IsAuthenticated flag will be false.  Otherwise the identity
 /// will be authenticated.
 /// </returns>
 public abstract UserIdentity AuthenticateUser(string token, UserSessionDurationType duration, String ipAddress, ExecutionResults result);
Beispiel #18
0
 /// <summary>
 /// Registers a new user.  The PasswordHash property should be the actual password.
 /// </summary>
 /// <param name="user">A user with a raw password which is turned into a password hash as part of registration.</param>
 /// <param name="duration">The amount of time that the initial session will be valid.</param>
 /// <param name="ipAddress">The internet address where the user is connecting from.</param>
 /// <param name="result">A ExecutionResults instance to add applicable
 /// warning and error messages to.</param>
 /// <returns>A boolean indicating success (true) or failure (false).</returns>
 public static UserIdentity RegisterUser(User user, UserSessionDurationType duration, String ipAddress, ExecutionResults result)
 {
     return(Provider.RegisterUser(user, duration, ipAddress, result));
 }
Beispiel #19
0
        private cs.UserIdentity AuthenticateUser(string name, string password,
                                                 UserSessionDurationType duration, string ipAddress, bool checkHistory,
                                                 bool allowUpdateHash, ExecutionResults result)
        {
            if (checkHistory)
            {
                var recentFailures = GetRecentFailedUserNameAuthenticationCount(name);
                if (recentFailures > AllowedFailuresPerPeriod)
                {
                    return(FailAuthenticateUser(name, ipAddress, result));
                }
            }
            User user = GetUserByName(name);

            if (user == null)
            {
                return(FailAuthenticateUser(name, ipAddress, result));
            }
            UserSalt salt = GetUserSalt(user.UserID);

            if (salt == null)
            {
                return(FailAuthenticateUser(name, ipAddress, result));
            }

            //this should get a named hashProvider used to originally hash the password...
            //  fallback to 'default' provider in legacy case when we didn't store the name.
            HashProvider hasher       = !string.IsNullOrEmpty(salt.HashName) ? HashManager.Providers[salt.HashName] : HashManager.DefaultProvider;
            var          passwordHash = hasher.Hash(salt.PasswordSalt, password, salt.HashGroup + BaseHashIterations);

            if (user.PasswordHash != passwordHash)
            {
                return(FailAuthenticateUser(name, ipAddress, result));
            }
            var session = new UserSession
            {
                CreatedDate    = DateTime.UtcNow,
                ExpirationDate = DateTime.UtcNow.AddMinutes(duration == UserSessionDurationType.PublicComputer ? PublicSessionDuration : ExtendedSessionDuration),
                UserID         = user.UserID,
                RenewalToken   = Guid.NewGuid()
            };
            var history = new AuthenticationHistory
            {
                IPAddress       = ipAddress,
                IsAuthenticated = true,
                UserName        = name,
                UserSession     = session
            };

            using (var scope = new System.Transactions.TransactionScope())
            {
                if (allowUpdateHash && (hasher.IsObsolete || user.PasswordHashUpdatedDate < DateTime.UtcNow.AddMonths(-1)))
                {
                    //update hashes on regular basis, keeps the iterations in latest range for current users, and with a 'current' hash provider.
                    hasher                       = HashManager.SelectProvider();
                    salt.PasswordSalt            = hasher.GetSalt();
                    salt.HashGroup               = new Random(DateTime.Now.Second).Next(HashGroupMinimum, HashGroupMaximum);
                    salt.HashName                = hasher.Name;
                    user.PasswordHash            = hasher.Hash(salt.PasswordSalt, password, salt.HashGroup + BaseHashIterations);
                    user.PasswordHashUpdatedDate = DateTime.UtcNow;
                    //starts as a lightweight transaction
                    SaveUser(user);
                    //enlists in a full distributed transaction if users and salts have different connection strings
                    SaveUserSalt(salt);
                }
                //either continues distributed transaction if applicable,
                //  or creates a new lightweight transaction for these two commands
                SaveUserSession(session);
                InsertUserHistory(history);
            }
            return(new cs.UserIdentity(history, this.Name));
        }