For a couple of days I am playing with MembershipReboot framework and being honest it looks very good. I would use it for my applications.
However, the question that I have is about its lockout functionality. I have tried to lock my account a couple of times, but it seems that nothing happens. Here is my configuration
<membershipReboot requireAccountVerification="true" emailIsUsername="false" multiTenant="false" allowAccountDeletion="true" passwordHashingIterationCount="0" accountLockoutDuration="00:05:00" passwordResetFrequency="0" accountLockoutFailedLoginAttempts="2" />
It seems that on my third or even fourth attempt I can login without any issue. Also I have investigated the DB that Membership uses and I cannot find any flags for a locked account.
My question is - is that lockout functionality comes already implemented out of the box or I have to do my logic there? If it is implemented, so can I enable it?
Account lockout in MembershipReboot uses two properties from security settings configuration
AccountLockoutFailedLoginAttempts (int, default: 10) : Number of failed password login attempts before the account is locked out.
AccountLockoutDuration (TimeSpan, default: 5 minutes): Duration an account will be locked due to too many failed password login attempts.
In your settings your are overriding the default values. So if you try more than 2 failed login attempts within 5-minute window your account is locked for another 5 mins from your last failed login. if you try to log in five mins after your last failed login you will be logged in as the account is not locked according to the lockout logic. If you try to log-in within 5 mins and your failed attempts have not exceeded you can still log-in.
Code is better than words(Check VerifyPassword method)
You will see all required properties for account lockdown in UserAccounts table. Namely LastFailedLogin and FailedLoginCount
protected virtual bool CheckHasTooManyRecentPasswordFailures(TAccount account)
{
var result = false;
if (Configuration.AccountLockoutFailedLoginAttempts <= account.FailedLoginCount)
{
result = account.LastFailedLogin >= UtcNow.Subtract(Configuration.AccountLockoutDuration);
if (!result)
{
// if we're past the lockout window, then reset to zero
account.FailedLoginCount = 0;
}
}
if (result)
{
account.FailedLoginCount++;
}
return result;
}
Related
I have a legacy application which is authenticating the user using as Identity Server 2. Where we are locking the user account after N number of failed attempts by updating below fields:
FailedPasswordAttemptCount
IsLockedOut
LastLockoutDate
FailedPasswordAttemptWindowStart
Now we are migrating from Identity Server 2 to 4 and I realized that new [AspNetUsers] table structure doesn't have all these fields. It contains only three fields as below:
LockoutEnabled: It specifies whether a user has opted for lockout or not.
AccessFailedCount: This fields gets incremented on each unsuccessful login attempts.
LockoutEnd: It specifies the locking timespan in minutes.
As I already told you that in IdServ4 [AspNetUsers].AccessFailedCount will increment on each unsuccessful attempts and after MaxFailedAccessCount it will reset the count to 0 and Set LockoutEnd with specified time span (As configured in startup file).
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 3;
options.Lockout.DefaultLockoutTimeSpan = 15;
})
After all this background information now I coming to my actual issue.
Let's suppose user visits our login page and made 2 unsuccessful login attempts and closed the browser and left for the day and then revisit our site a few days later and tries one more unsuccessful attempt now he crossed MaxFailedAccessCount and then his account gets locked (Code is written in Identity Server library) but as per user view he made only one unsuccessful attempt.
What I am trying to do:
As I am not using EntityFramework for database interaction and written own Stored Procedures for it and implemented all stores myself so I can introduce LastAccessFailedDate field in [AspNetUsers] table and compare it with current date time when the user made an unsuccessful login attempt and if it's less than configured timespan I will just reset AccessFailedCount to 1 and existing value +1;
Original Code:
public Task<int> IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken)
{
user.ThrowIfNull(nameof(user), cancellationToken);
return Task.FromResult(++user.AccessFailedCount);
}
My Approach:
public Task<int> IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken)
{
user.ThrowIfNull(nameof(user), cancellationToken);
// Lets suppose _configuredResetTime configured to 60 minutes
If(user.LastAccessFailedDate > DateTime.Now.AddMinutes(_configuredResetTime))
{
user.AccessFailedCount += 1;
}
else
{
user.AccessFailedCount = 1;
}
user.LastAccessFailedDate = DateTime.Now;
return Task.FromResult(user.AccessFailedCount);
}
Note: I am not sure whether there is an alternative which has been provided by IdServ4 library or do I need to implement it myself.
Expected result:
So I am wondering can we reset AccessFailedCount after configured timespan if a user is not active on login screen like we do in IdServ2 using [aspnet_Users].FailedPasswordAttemptWindowStart field.
Any suggestions are welcome, Thanks!
I am trying to figure out how to fight a brute force attack on my website. Based on all the research I have done the top answers were Account Lockout & Captcha.
If I lock out a user then I am denying them service for x amount of time. This means that if an attacker were to attack 10 different accounts he will lock them all. Then when time is up he will lock them again. Basically he can keep at it and keep the users locked out indefinitely. The users can contact me but that is now 10 tickets I would have to deal with and I'd rather avoid that work if its possible. So what I am failing to understand exactly is how is this then useful? The attacker might not get into account but they will cause me and users a lot of grief.
How do I combat this? Ip banning seems pointless as it can be changed fairly easy.
You can add an incremental delay that doubles after each failed login attempt, after a handful of login attempts the delay gets too long for brute force to work (e.g. after 20 attempts the delay is 6 days).
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel viewModel, string returnUrl)
{
// incremental delay to prevent brute force attacks
int incrementalDelay;
if (HttpContext.Application[Request.UserHostAddress] != null)
{
// wait for delay if there is one
incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress];
await Task.Delay(incrementalDelay * 1000);
}
if (!ModelState.IsValid)
return View();
// authenticate user
var user = _userService.Authenticate(viewModel.Username, viewModel.Password);
if (user == null)
{
// login failed
// increment the delay on failed login attempts
if (HttpContext.Application[Request.UserHostAddress] == null)
{
incrementalDelay = 1;
}
else
{
incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress] * 2;
}
HttpContext.Application[Request.UserHostAddress] = incrementalDelay;
// return view with error
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View();
}
// login success
// reset incremental delay on successful login
if (HttpContext.Application[Request.UserHostAddress] != null)
{
HttpContext.Application.Remove(Request.UserHostAddress);
}
// set authentication cookie
_formsAuthenticationService.SetAuthCookie(
user.Username,
viewModel.KeepMeLoggedIn,
null);
// redirect to returnUrl
return Redirect(returnUrl);
}
There's more details at this post
Don't display the user id used to log in publicly. Have a separate display id. For example, they might log in with their email address and choose a different name to display. If an attacker doesn't have the user id then he can't make repeated login attempts and lock another user out.
You could use a PoliteCaptcha, which only displays the captcha if JavaScript is disabled (as in most automated scripts) or when the first submit attempt fails. This makes the captcha invisible to most of your normal users, but a PITA for spammers.
I have an MVC4 single application page. In the log-in page there are 3 fields: user, password and "remember me" checkbox.
The C# login code is this:
if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return Json(new { success = true, redirect = returnUrl });
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
I want to do this:
If a user logs in with "remember me" true - The cookie will stay until the user logs off.
If the user logs in with "remember me" false - The cookie will expire after 3 hours.
In web.config I have this code:
<authentication mode="Forms">
<forms loginUrl="~/" timeout="180" cookieless="UseCookies" slidingExpiration="false" />
</authentication>
<sessionState timeout="180" />
The problem is when the user doesn't touch the page for a short time (10-30 mins usually), and then the user tries to do something in the page - there is an error
"Authorization has been denied for this request."
(Although sessionStation is more than 30 minutes!)
After the user refresh the page - if the cookie hasn't expired yet - everything works fine. But of course I don't want to make the user refresh the page every 15 minutes or so, it's a single-page-application.
So, I tried to change slidingExpiration to "true" and create a ping (with ajax) every 17 minutes and it really stopped the session expiration and the annoying message - but the cookie didn't expired after 3 hours (I logged in with remember me 'false')!
What can I do?
Right click your application pool from IIS management console and look for "Idle time-out(minutes)".
You can adjust the setting to 0 (zero) which effectively disables the timeout so that the application pool will never shut down due to being idle.
I would double check your IIS settings. See what your App Pool Idle Timeout value is set to. By default it's 20 minutes. When the App Pool goes idle (no users accessing the app pool) for twenty minutes, you will loose session state (All data stored in the session will be cleared).
With more users this problem would work itself out, but presuming you are the only person testing, increasing this value to something greater than 180 minutes will prevent the timeout, or you could set the value to zero to disable the app pool idle timeout altogether.
See this answer for information on checking your app pool timeout in IIS Express... https://stackoverflow.com/a/10222419/386856
Do note that a dead app pool can take several seconds to re-spawn. It may be beneficial to increase this value anyways. This will prevent users from having an extremely slow experience if they happen to be the person that's unlucky enough to have to wait for the app pool to restart.
Update
To allow for a change in timeout for users who don't click remember me, you can create a custom attribute or you could modify the FormsAuthentication timeout via C#. Here are good references on setting the timeout via code. https://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration.timeout%28v=vs.110%29.aspx and https://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration(v=vs.110).aspx If you want the timeout to expire right at 3 hours, even if the user has activity be sure slidingExpiration is false in your web config. If you want the timeout to expire 3 hours after the user's last activity be sure slidingExpiration is true. So before you set the cookie try something like the following (Be sure to check the OpenWebConfiguration path):
System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("/aspnetTest");
AuthenticationSection authenticationSection = (AuthenticationSection)configuration.GetSection("system.web/authentication");
FormsAuthenticationConfiguration formsAuthentication = authenticationSection.Forms;
formsAuthentication.Timeout = System.TimeSpan.FromHours(3);
formsAuthentication.SlidingExpiration = true;
I solved this by just creating my own encrypted cookie which will persist the user's session if present. In an action filter attribute, check if the user's session is expired, check for this cookie. If the cookie exists, verify the information and reestablish the session. If the user doesn't have a session, doesn't have a cookie, or the encrypted credentials are incorrect, redirect the user to the login page. Since everything is handled in the code, there is no guess work on the server settings.
On login with remember me checked:
if (RememberMe ?? false)
{
var authCookie = new HttpCookie(Config.AuthorizationCookie);
authCookie.Values.Add("ID", Crypto.EncryptStringAES(UserSession.Current.Account.ID.ToString(), Config.SharedSecret));
authCookie.Expires = DateTime.Now.AddDays(30);
AuthorizationCookie = authCookie;
}
Response.AppendCookie(AuthorizationCookie);
On page visit without session (done inside an attribute attached to necessary controllers and actions):
_account = UserSession.Current.Account;
// check if there is currently an account in the session
if(_account == null)
{
// check the user authorization cookie for a user id
HttpCookie authCookie = HttpContext.Current.Request.Cookies[Config.AuthorizationCookie] ?? new HttpCookie(Config.AuthorizationCookie);
if (authCookie.HasKeys && !String.IsNullOrEmpty(authCookie["ID"]))
{
// decrypt the user id for database lookup
_userID = Crypto.DecryptStringAES(authCookie.Values["ID"], Config.SharedSecret);
}
}
Re-establish the session if necessary (done inside a database connection)
// called within a database connection's using statement
protected override void Query(DatabaseContext db)
{
// check the database for the user account
if(_account == null && !String.IsNullOrEmpty(_userID))
{
int tempID;
int? id;
id = int.TryParse(_userID, out tempID) ? tempID : (int?)null;
if(id.HasValue)
{
_sessionRestored = true;
_account = db.User.Find(id);
if(_account != null)
{
_account.LastLogon = DateTime.UtcNow;
db.SaveChanges();
}
}
}
}
Finally restore the session:
if (_account != null)
{
// set the current account
UserSession.Current.Account = _account;
if(_sessionRestored)
{
UserSession.Current.Refresh();
}
}
Your code may look a bit different but that is the crux of how I did it.
I am using ADFS 2.0 for quite some time and I understand how things work. I've done dozen of custom RPs, custom STSes as well as using the ADFS as the relying STS.
However, I have a simple requirement which I still fail to fulfill.
I want my users to be forced to relogin after some fixed time. Let's say 1 minute, for test purposes.
First, I've made some corrections at the RPs side. It seems that for unknown reason, the RP retains the session even if the token's validTo points back in time. This contradicts what Vittorio Bertocci says in his book (page 123) where he shows how to perform sliding expiration - he says that "The SessionAuthenticationModule will take care of handling the expired session right after". Well, for me it doesn't, however I have found a trick here http://blogs.planbsoftware.co.nz/?p=521 - take a look at the "if" clause:
sam.SessionSecurityTokenReceived +=
( s, e ) =>
{
SessionAuthenticationModule _sam = s as SessionAuthenticationModule;
DateTime now = DateTime.UtcNow;
DateTime validFrom = e.SessionToken.ValidFrom;
DateTime validTo = e.SessionToken.ValidTo;
try
{
double halfSpan = ( validTo - validFrom ).TotalSeconds / 2;
if ( validTo < now )
{
_sam.DeleteSessionTokenCookie();
e.Cancel = true;
}
}
catch ( Exception ex )
{
int v = 0;
}
};
This trick fixes the issue at the RPs side. When the token is invalid the application clears it out and redirects to the login page.
Now comes the problem. My login page uses the FederatedPassiveSignIn control. When clicked, it redirects the browser to the ADFS.
But ADFS happily creates a new session without any prompt for the user.
I have set the token's lifetime for this RP to 1:
Set-ADFSRelyingPartyTrust -Targetname "myrpname" -TokenLifetime 1
and using Get-ADFSRelyingPartyTrust I can see that it's set to 1 (I even print the token validTo on my page to confirm that this is set correctly).
Then I set ADFS properties with ADFS-SetProperties:
ADFS-SetProperties -SsoLifetime 1
ADFS-SetProperties -ReplyCacheExpirationInterval 1
ADFS-SetProperties -SamlMessageDeliveryWindow 1
but still no luck. I am stuck now.
The scenario works correctly with my custom STS where the validity of the STS session is based on a Forms cookie - if I set the STS's forms cookie timeout to 1, after 1 minute of inactivity within my RP application I am redirected to the login page of my RP which then redirects to the STS which presents its login page.
However, this is not the case with ADFS 2.0. After a minute of inactivity, I am redirected to the login page of my RP which redirects to ADFS's login page which in turn redirects back happily just like the session would be still active within ADFS.
I would like someone to:
(1) take a look at the hack described at the top and explain why an expired token is not automatically rejected and such ugly hack is needed
(2) explain how to properly timeout the session at the ADFS 2.0 side so a request to renew the token is guarded with a login page.
Thanks in advance.
edit
I can confirm that setting all above parameters to 1 minute makes the ADFS session invalid after 5 minutes (or more). That's strage and it seems that either I am making a basic mistake or 5 minutes is the minumum acceptable value.
My (2) from above is now then just to confirm and explain my observation.
As per comments above (joint effort with the OP) the Freshness property on the FederatedPassiveSignIn instance should be set to 0.
According to http://docs.oasis-open.org/wsfed/federation/v1.2/ws-federation.html this indicates for the IP/STS to re-prompt the user for authentication before it issues the token.
You could also try changing ADFS from windows integrated authentication to forms based authentication. You will probably still have to monkey with the freshness property but now your users will have to enter their credentials even if they are on the same network as your AD.
This article explains it pretty simply:
http://social.technet.microsoft.com/wiki/contents/articles/1600.aspx
It's quite strange that setting the TokenLifetime value didn't work . The article in MSDN explains timeout as a straight forward setting - by assigning TokenLifetime value. I'm interested to know whether the setting described in MSDN is correct. If that didn't help, then it's right time to revise that article. Hope that will be a big help to those who are facing this issue.
var query = from p in AdminModelContext.Users
where p.UserName == model.UserName && p.Password == encryptPassword
&& p.IsDeleted == false
select p;
IList<Users> userList = query.ToList();
if (userList.Count() > 0)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (CheckUrl(returnUrl))
{
return Redirect(returnUrl);
}
SetRoleForUser(userList[0].RolesId);
LoggerService.Info(string.Format("Login Successful for the user : {0}",
model.UserName));
return RedirectToAction("Index", "Home");
}
I am using the following code to Login through my website. The problem that I am facing is when I logged in with a user on a specific browser and at the same time login with a different user on a different browser, and then I delete the user(logged in on the other browser). Still I am able to navigate through the pages with the deleted user logged in.
I am not finding a fair solution to put authentication logic on every page. My website is in MVC model and using Form based authentication.
Please suggest how can I put the logged in user session validation and achieve this.
None of the answers so far actually acknowledge the question.
Lets look at the control flow:
User A enters log in page, supplies valid credentials
User A is issued Ticket A.
User B enters site, supplies valid credentials.
User B is issued Ticket B.
User B then revokes User A's access CREDENTIALS.
At this point nothing happens to Ticket A. Because the Ticket is independent on the credentials. When Ticket A expires they will then be required to present their credentials and it will fail login.
So what you've noticed that kicking a live user out of your site is actually pretty hard. As you've realized the ONLY solution is to have authentication logic on EVERY request. That unfortunately is really heavy.
In the login system I built I handled this aspect by creating 2 tickets, 1 ticket that's stored in the Forms Auth ticket as normal that has a big duration, and a ticket that's stored in HttpRuntime.Cache, I set the cache expiration to 15 minutes on this ticket.
On every page request I check to see whether a user has a ticket in the cache (based off their Forms Auth ticket information), at this point if they have no ticket I do a user data refresh and poll the user database. If the user has become suspended or deleted they will be logged out at then.
Using this method I know that my site can disable a user and within 15 minutes that user will be barred from the site. If I want them immediately barred I can just cycle the app config to clear the cache and FORCE it to happen.
Normally if you have the [Authorize] attribute defined for an Controller or an Action the Authentication is checked on every post back.
The build in MembershipProvider handles all that for you. But it seems you are using your own user Database. Then you have to implement your own MembershipProvider, IPrincipal and MembershipUser and have this added to your Web.config replacing the default one.
More you'll find here how to implement your own MembershipProvider: http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx
My suggestion is to create an empty MVC project and have a look at the default authentication mechanism. And if your building a new Application with a new Database, try to use the default authentication.
Your validateUser function in your own MembershipProvider could look like this.
public override bool ValidateUser(string username, string password)
{
bool isValid = false;
bool isApproved = false;
string pwd = "";
using (AdminModelContext db = new AdminModelContext())
{
var user = db.Users.FirstOrDefault(u => u.UserName == username);
if (user != null)
{
pwd = user.Password;
isApproved = user.IsApproved;
if (CheckPassword(password, pwd))
{
if (isApproved)
{
isValid = true;
user.LastLoginDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
try
{
db.SubmitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
else
{
UpdateFailureCount(username, "password");
}
}
}
return isValid;
}
I see the problem now. I dont know how this works in MVC, but by using Authenticate_Request, you can validate if the user is still valid. The business logic may also double check if the user is still valid. But as far as I know, there is no way of iterating all the open sessions and killing the requierd ones, even in that case, the authorization cookie should be double checked on Session_Start event.
Another options is adding a global invalidated_users list on the application, and then checking that user against the invalid list. This list should only contain users that are invalidated after the application has restarted.
Link for Reading All Users Session:
http://weblogs.asp.net/imranbaloch/archive/2010/04/05/reading-all-users-session.aspx