Manage User Account Lockout
On this page
The Curity Identity Server provides a username/password (HTML Form) authenticator out-of-the-box that can lock a user based on a configured number of failed authentication attempts. This can be coupled with the rolling-session-period setting to keep the user locked for a configured number of minutes. The lockout is session-based, and the number of failed login attempts is stored in a user session.
Sometimes, however, it's desirable to control the lockout centrally to not rely on the user session itself; and with that, lock the user across any session. This article outlines a way to configure a centralized lockout of a user with event listeners. This can be accomplished using either a bucket or the default accounts table, and a custom SQL query used by the Credential Manager in use.
Overview
The solution will leverage two Event Listeners, one for FailedVerificationCredentialManagerEvent
and one for SuccessfulVerificationCredentialManagerEvent
.
When the FailedVerificationCredentialManagerEvent
event triggers, a counter tied to the user is incremented and persisted in a data store. When the counter reaches a configurable threshold (maxLogin
), a timestamp value of current time + a configurable lockout time is stored alongside the counter. The timestamp is in UNIX time format and represents the time the user is locked until. For every subsequent failed login attempt, the timestamp will be updated.
If a SuccessfulVerificationCredentialManagerEvent
is triggered, the timestamp will be set to 0
, and the failed login counter will be set to 0
, effectively resetting the lockout. This event only triggers if the user credentials are correct and the stored lockout timer has passed.
The Credential Query in the Data Source used by the Credential Manager needs to be updated to also read the timestamp. The query will compare the timestamp and current time and will not produce a result if the account is locked. When no result is found, the authentication fails.
Buckets or Accounts
Depending on the Curity Identity Server deployment, the lockout counter and time stamp could be stored in different data sources.
If the default Curity Identity Server database schema is used along with the accounts table to hold account information, the lockout counter and time stamp can be stored there. This will allow for a more optimized credential query since SQL JOIN
can be avoided.
If the default accounts table is not used, it is possible to store this information in the buckets table instead. However, this forces the credential query to perform a SQL JOIN operation, which could negatively impact performance.
- Configure a new event listener, System → Event Listeners → New Event Listener, and name it
account-lockout
. Select the Script Event Listener as the type. - Enable the
Account Manager
and choose the Account Manager used by the Authenticator. - Next to Procedure, click New. Give the Procedure a name,
account-lockout-procedure
, and click Create. - In the JavaScript editor that pops up, replace all the code with the following:
var listeners = {'FailedVerificationCredentialManagerEvent': function (context, event) {var maxLogin = 3; //Number of attempts before user is locked outvar lockoutTime = 60; //In secondsvar incrementedCounter;var subject = event.getSubject();logger.debug("Failed login by user {}.", subject);var am = context.getAccountManager();var account = am.getByUserName(subject);logger.debug("Attributes retrieved from accounts for user {}: {}", subject, account);//No value found for failedAuthenticationAttemptsif (account.failedAuthenticationAttempts === null){incrementedCounter = 1;}else{incrementedCounter = parseInt(account.failedAuthenticationAttempts) + 1;}if(incrementedCounter < maxLogin){account.failedAuthenticationAttempts = incrementedCounter;account.lockoutUntilTime = 0;am.updateAccount(account);}else{var currentTime = Math.round(new Date().getTime() / 1000);var lockoutUntilTime = (currentTime + lockoutTime); //Add how long the user should be locked out to the current timeaccount.failedAuthenticationAttempts = incrementedCounter;account.lockoutUntilTime = lockoutUntilTime.toString();am.updateAccount(account);}},'SuccessfulVerificationCredentialManagerEvent': function (context, event) {var subject = event.getSubject();logger.debug("Successful login by user {}. Resetting login counter in accounts table.", subject);var am = context.getAccountManager();var account = am.getByUserName(subject);account.failedAuthenticationAttempts = 0;account.lockoutUntilTime = 0;am.updateAccount(account);}};
Configure parameters
Ensure to configure the maxLogin
and lockoutTime
variables in the script to match the requirements.
Credential Query
The Credential Query configured on the Data Source (used by the Credential Manager that the Authenticator is using) must be updated. Do so here: Facilities → Data Sources → data source used → Credential Query.
IF (SELECT JSON_VALUE(attributes, '$.lockoutUntilTime') FROM accounts WHERE accounts.username = :subjectId) IS NULLSELECT account_id,username AS userName,passwordFROM accounts WHERE username=:subjectId;ELSESELECTaccount_id,username AS userName,passwordFROM accounts WHEREusername=:subjectId AND( (SELECT JSON_VALUE(attributes, '$.lockoutUntilTime')) <( SELECT DATEDIFF_BIG(second, '1970-01-01 00:00:00', GETUTCDATE())) );
MS SQL specific
The SQL query used in both solutions is specific to MS SQL and is tested with version 2017-CU24-ubuntu-16.04. But, it should work with other versions of MS SQL as well.
It would also be possible to achieve the same approach using different databases, as long as they enable similar functions to read the JSON blob and can handle IF/ELSE statements in the query.
Optimization
The complexity of the SQL query could have an impact on performance due to using the JSON_VALUE
function. To improve performance related to the JSON_VALUE
function, read this post: Query performance for JSON objects inside SQL Server using JSON_VALUE function - Blog IT.
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial