How to reset AccessFailedCount automatically after specified time in Identity Server 4 - c#

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!

Related

How to correctly update database when users session expires for ASP.NET web application using the new .NET 6 format?

When a users session expires I want to update the database to switch their status to offline but I cannot find the right way to do this as I know that they can either close the browser/page or the session could expire. I'm using CookieAuthentication which has a timer of 30 minutes.
I would like some advice on the best way I can do this, either having Javascript but I don't know if this can work with C# to run a database script. I also need this per user to when the user logs in the database updates a table saying they are online and when the user logs out it also updates it saying that user is offline but obviously there is multiple ways a user can be 'Offline'.
I am new to coding in ASP.NET and using .NET Core 6. I'm just looking for some advice on the best way this can be done.
From How to find if ASP.NET session is expired
How to check if session is expired using ASP.NET code
We'll define expired session as situation when Session.IsNewSession is true (it is a new session), but session cookie already exists on visitor's computer from previous session. Here is a procedure that returns true if session is expired and returns false if not.
[ C# ]
public static bool IsSessionExpired()
{
if (HttpContext.Current.Session != null)
{
if (HttpContext.Current.Session.IsNewSession)
{
string CookieHeaders = HttpContext.Current.Request.Headers["Cookie"];
if ((null != CookieHeaders) && (CookieHeaders.IndexOf("ASP.NET_SessionId") >= 0))
{
// IsNewSession is true, but session cookie exists,
// so, ASP.NET session is expired
return true;
}
}
}
// Session is not expired and function will return false,
// could be new session, or existing active session
return false;
}

Can't get Unity Firebase userID

I'm trying to save the user data to the firebase realtime database directly after the user has been created. But the problem is not the saving, but the UserID. I save also save the user ID that i get from CurrentUser. and then i check in the realtime database and saw that the ID that stored was from a last user who recently created. And i check it in the editor by getting the current user Email and it showed the last user Email not the current user who are creating at the moment. Can someone help me to get the current user ID and not the last user id.
You guys can see the image from the links.
What ID should be
The last user ID showing up instead You guys can see that the ID don't event match. I did try redo the project and looking at the videos that from firebase it self. I really have no ide what to do, i am stuck for 3 days now.
public void SaveNewUserInCode(string userId, string Name, string Email) {
var currentUser = FirebaseAuth.DefaultInstance.CurrentUser;
string userNameId;
if (currentUser != null)
{
userNameId = currentUser.Email;
user = new User(userId, Name, Email);
string Json = JsonUtility.ToJson(user);
reference.Child("Users").Child(currentUser.UserId).SetRawJsonValueAsync(Json);
Data.text = userNameId;
}
}
It would be helpful to see the code that invokes SaveNewUserInCode.
I see a few potential dangers with the code you've posted:
The first is that any call (other than Auth.SignOut) is asynchronous. If you're caching userId immediately after Auth.SignInWithEmailAndPasswordAsync, you'll likely have the previous user still in Auth.CurrentUser (until the related task completes). See my related post on all the ways to wait for a task in Unity if you think this is the issue.
The second, especially if Email is sometimes null, you may be automatically calling Auth.SignInAnonymouslyAsync every time your app starts (perhaps old logic, I usually start my prototypes with anonymous users and later on add real accounts when it's time to do some user testing). This will always overwrite your current user even if you were previously signed in anonymously. You should always check Auth.CurrentUser before calling any of the Auth.SignIn methods, but definitely make sure that you don't have a stray Auth.SignInAnonymouslyAsync laying around.
If the issue is threading, I believe the following logic will fix your problem:
var auth = FirebaseAuth.DefaultInstance;
auth.SignInWithEmailAndPasswordAsync(email, password).ContinueWithOnMainThread(task => {
// omitted : any error handling. Check task's state
var user = task.Result;
SaveNewUserInCode(user.UserId, user.DisplayName /* or however you get Name */, user.Email);
});

Identity Server 4 - GetProfileDataAsync called multiple times, results in multiple database entries

I have a rather different use case where I have to track the login history, and to track them I'm creating a new GUID in the IProfileService and adding it in the claims and also to the DB , to continue front end task.
The issue is I get multiple DB entry for a single login click, which starts displaying two records per login.
The GUID is used to track the current login session.(We have concurrent login scenario)
Question 1 - Is the GUID creation within this function the right thing, if not where to do it?
Question 2 - Does IDSvr provide a Unique Id for each login, so that i can just use it in the claims.
Question 3- How I stop/minimize the other DB calls made because of multiple calls.
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = DataBaseUserRetrive();
var claims = new List<Claim>
{
//All Claims from user
};
string deviceIdGuid = Guid.NewGuid().ToString();
Claim deviceId = new Claim("device_id", deviceIdGuid ,ClaimValueTypes.String);
claims.Add(deviceId);
await databaseCall(user,deviceIdGuid );
context.IssuedClaims = claims;
}
PS :- I'm quite new to Identity Server
To expand on my comment on the OP - I think a custom implementation of IUserSession (or inheriting and overriding virtual methods in DefaultUserSession) is probably the correct place to intercept session related events and add custom behaviour and logic.
The default implementation is here: https://github.com/IdentityServer/IdentityServer4/blob/3.0.2/src/IdentityServer4/src/Services/Default/DefaultUserSession.cs
Check out the method EnsureSessionIdCookieAsync() - that's probably a good place to wire in any custom logic.
This session ID is what's sent to clients and forms part of the protocol - in particular the front channel logout and session monitoring specs.

MembershipReboot lockout functionality issue

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;
}

Cookies and Sessions Expiration in .NET

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.

Categories