A brute force attack (BFA) is when an attacker makes thousands of attempts on an HTTP resource in an attempt to guess common values for an input. For example, guessing a username and password on a login page. In most cases there are many combinations of usernames and passwords, so the attacker needs to be able to execute as many combinations as possible per second to find matches (i.e. 3-5k+).
For certain apps, the user account should be locked after a number of failed attempts so that the user's data is protected if the failures are from an attacker. There are app scenarios where locking a user account would disrupt a legitimate user's ability to use their own account, so it's up to the app owner if they wish to enable this feature. Either way, it's important that the API provide the means to lock user accounts after a specific number of failed login attempts.
Requirements:
Store a 'enabled' true/false property in an external property config so that this feature can be turned on/off for any given server and any time.
Store a 'max login attempts' property in an external property config so that each app can determine the count that is appropriate
Create an event listener or http filter that monitors failed FORM login attempts
Create an event listener or http filter that monitors failed BASIC AUTH login attempts
Lock user feature approach
If the feature is enabled (based on external config property), then execute
Lookup the User by the username provided
If the User exists, then increment the number of failed login attempts (the User should have a "failedLoginAttempts" property stored on the User object
Check if the user's new failedLoginAttempts > max failed login attempts (from property config), then lock the user account (user.isLocked = true or user.isEnabled = false)
The user should have a locking or disable feature that would prevent further login
The user should be able to complete the forgot password process to unlock/enable their account
NOTE: The entire workflow should be completed outside of an authenticated session. Do not allow the user to ever be authenticated during this process because that would give them access to probe the API and other areas of the server! Once the password reset process is complete, then force the user to authenticate with their new credentials.
User clicks "Login" on a client app
Passes the "client_id", "client requested grants", and "redirect_url" to the API /oauth/authorize endpoint
API server displays a Login page asking for Username & password using the existing OAuth2 process.
User forgets their username / password and clicks "Forgot Password" link underneath the Password input.
The client parameters stored in the session need to somehow be preserved so that when the password is reset, the user can be redirected to the login page and resume the process of authenticating using OAuth2
API Server displays a simple forgot password page with a "username" and "mobile phone number" input field
Must enter the username associated with the account
Must enter one of the registered mobile phone numbers associated with the account
Phone number must have a country code + phone number formatted to the country selected. Must be parsable to a E.164 phone number.
A "Cancel" button is displayed that will redirect the user back to the login page (preserving their original client parameters sent in on the /oauth/authorize request).
User clicks "Submit" with a username and mobile phone number
API server writes an ActionLog to the database with the "Reset Password Request" action log and the "username" in a discrete field (new column)
Query ActionLog table for the given username within the past 20 minutes. If >= 5 Password Reset Request records, then return an error message
Error message should read something like "Too many password reset attempts. Password reset is locked for 20 minutes for the requested username.".
Do not include the username or phone number in the error message.
API Server queries the user database for a User with the given username and mobile phone (make sure the mobilePhone.isDeleted=false)
If a User is found, then store the User.id within the HTTP Session for reference within the User Verification process.
API Server initiates a verification code text message to the found user's mobile phone
Initiate the SMS text message send as an asynchronous background job
If there is any delay in the SMS delivery process, then an attacker will be able to tell when they have a match if they are guessing. The delay itself indicates that extra work is being done to send the SMS message.
Always returns a message "If the username and phone number are correct, then a verification code will be sent to your mobile phone". Even if the username and mobile phone do not match, always display this message.
Store the verification code on the mobile phone record with an expiration, just like the normal verification process
Do not use the normal User Verification servlet filter because that requires an authenticated user... the user is not authenticated at this point in the process, so reuse whichever parts of the User Verification process make the most sense.
API Server displays a Verification Code page with a single input for the code
A reference to the user.id should be stored in the HTTP session if a valid user was identified in the prior step.
Display this page even if a user was not found for the given username + mobile phone number on step 4.
Included in a page is a "Cancel" button that will take the user back to the Login page where they can try to reenter their password
Be sure to retain the original login request parameters so that the OAuth process will continue after a successful login.
User enters the verification code and clicks submit
Query ActionLog table for the given username within the past 20 minutes. If >= 5 "Reset Password: Verification Code Attempt" are found, then return an error message
Error message should read something like "Too many password reset attempts. Password reset is locked for 20 minutes for the requested username.".
Do not include the username or phone number in the error message.
The verification code + the user.id in the HTTP session are used to find a matching mobile phone associated with the user.id.
If a user.id is not in the HTTP session, then execute the User lookup anyway using a fictitious user.id of "0" (zero).
It's important NOT to give away to an attacker that we are executing a different process for nonexistent users.
By conducting the same workflow even tho we know the user doesn't exist, we are keeping the latency between calls almost exactly the same. Virtually unnoticeable if we are working with a valid user or not.
If a user phone is found for the given user.id, then render/internal redirect to the Security Questions page
If a user.id is not found in the HTTP Session or a user phone is not found that matches the verification code for the given user.id, then redirect the user back to the Verification Code entry page with an error stating "Incorrect verification code. You have 4 more attempts."
Insert an ActionLog record of "Reset Password: Verification Code Attempt" with the username in a discrete field.
Security Questionnaire page
Upon successful entry of the verification code, display the Security Questionnaire page
This page cannot be accessed directly via URL, but only accessible by internal redirect/render by the Verification Code step.
If no user is found within the session, then redirect to the login page with no error message
discrete field.
Verify that a User.id is loaded within the session
Lookup the user object if not already loaded
Check for a HTTP session attribute that states that all other steps in the process have been completed successfully for the given user (user.id, verification code, etc.)
Display all security questions associated with the user's profile (should be limited to 5)
Display "password" input boxes for each security question (NEVER have multiple choice), not to exceed 255 characters
Validate that each security question is answered before the page can be submitted
Submit Security Questions page
If no user is found within the session, then redirect to the login page with no error message
Compare each security question answer with the encoded value from User's security question answer table
Security question answers MUST be password encoded in the database
If any one question is wrong, then redisplay the Security Questionnaire page with a message stating that "One or more of the answers are incorrect. 4 attempts remaining"
A total of 5 attempts are allowed.
Keep track of attempts within the Session
Update the error message with a decremented count after each failed attempt
Make sure that the first time the user has the security questions display, that the count is reset to 0.
If the number of failed attempts == 5, then clear the HTTP session of the User and redirect the user to the
If all of the security question answers are correct, then internally redirect to the Password Reset page with an error message stating "Security question failed attempts exceeded the limit. Password reset has been disabled for 20 minutes"
Password reset page
Upon successful validation of the security question answers, display the password reset page
This page cannot be accessed directly via URL, but only accessible by internal redirect/render by the Security Questionnaire save/validation step.
Display a "Password" input field with an input type of "password"
Display a "Confirm Password" input field with an input type of "password"
Display a "Cancel" button that will redirect the user back to the Login page (preserving their original request parameters so that they can complete the OAuth login workflow)
Display a "Reset Password" button that will save the new password
Clicking this button will verify that the Password and Confirm Password match using javascript on the page
Clicking this button will POST the password to the save password server action
Reset password action
Check for a HTTP session attribute that states that all other steps in the process have been completed successfully for the given user (user.id, verification code, security questions, etc.)
If any of the attributes required to access this page are not found, then redirect to the login page with no error message
Verify/load the user from the user.id in the http session
Compare the given Password with the Confirm Password to ensure they match exactly.
If the passwords do not match, then redirect back to the "Reset Password Page" with an error message that the Password and Confirm Password do not match.
Allow the user to retry as many times as they require
Hash encode the Password field and save to the User record in the database
Redirect the user to the Login page with a success message of "Password reset successful." (preserving the original client request parameters from the /oauth/authorize request)
CSRF Tokens on every page with a form
Every page must have CSRF tokens for the password reset and Login page functionality
Enabling Spring CSRF tokens might not work unless they can be limited to specific URL paths (and not for the web services /api).
Might need to create a custom CSRF token process for this workflow.
NOTE: Whenever we display a FORM, we need to include some sort of "token" in the session that allows the user to proceed to the next step in the reset password workflow. We must prevent an attacker from entering a valid username + phone and then simply posting the password reset FORM without going through the security questions. There should be some value in the HTTP session that notifies the next workflow step that the prior step was completed successfully.
NOTE: Make sure the ActionLog.username field has an index on it so that lookups on username are performant
Provide the ability to exclude certain URL resources from being logged. These resources might include unrestricted images, stylesheets, javascript files, or other static content that is not pertinent to user data or activity.
Requirements
Store a list of resource paths in an external property config that should be EXCLUDED from action logging.
Example using AntPath matcher: ['/resources/images/', '/static/css/']
Examine the incoming request URL and match the URL against the list of excluded resources
If the URL matches an excluded resource address, then skip the action logging.
If no match, then log the incoming request/response
A secure web service endpoint that requires the user in need of verification to be authenticated by either a username/password or through security questions. Invoking the /verify POST web service requires that the body contains the code delivered to the user's mobile phone via SMS. The code provided in the web service will be verified against the code stored in the User's account.
sends a unique code to up to 5 mobile phones from the authenticated user profile
no parameters
requires the user to be authenticated
no permissions besides being authenticated
/v1/verify POST receives a single body param: "code"
looks up the "code" against the authenticated user's mobile phone codes. If one matches, then the phone is "validated" and the user account is considered "validated"
user.is_verify_required = true/false
user_phone.verify_code = code value
user_phone.verify_code_expires_on = date
user_phone.is_validated = true/false
JSON encoded {"code":"234324"}
requires the user to be authenticated no permissions besides being authenticated
Implicit authentication should work the same way as Java
Overview
The user instructs the client 'app' to make API requests on the user's behalf.
The client initiates the authentication using their client ID, but does not provide a password because the user will be required to enter their own username and password to authorize the client.
The API will load both the Client and User objects into the session
This authentication method is the preferred method for a web or mobile app
Web Service: http://localhost:8080/oauth/authorize
A secure web service endpoint that requires the user in need of verification to be authenticated by either a username/password or through security questions. Invoking the /verify/send web services initiates the delivery of a verification code to the mobile phone on the user's account. The code that is delivered will be a 6-digit code that will be stored securely in the database using the same password hashing method (bcrypt). A mobile phone is required at account creation.
A brute force attack (BFA) is when an attacker makes thousands of attempts on an HTTP resource in an attempt to guess common values for an input. For example, guessing a username and password on a login page. In most cases there are many combinations of usernames and passwords, so the attacker needs to be able to execute as many combinations as possible per second to find matches (i.e. 3-5k+).
For certain apps, the OAuth2 client account should be locked after a number of failed attempts so that the user's data is protected if the failures are from an attacker. There are app scenarios where locking an OAuth2 client account would disrupt a legitimate client's ability to facilitate access to the API, so it's up to the app owner if they wish to enable this feature. Either way, it's important that the API provide the means to lock OAuth2 client accounts after a specific number of failed login attempts.
Requirements:
Store a 'enabled' true/false property in an external property config so that this feature can be turned on/off for any given server and any time.
Store a 'max login attempts' property in an external property config so that each app can determine the count that is appropriate
Create an event listener or http filter that monitors failed FORM login attempts
Create an event listener or http filter that monitors failed BASIC AUTH login attempts
Lock OAuth2 client feature approach
If the feature is enabled (based on external config property), then execute
Lookup the OAuth2 Client by the client identifier provided
If the Client exists, then increment the number of failed login attempts (the Client should have a "failedLoginAttempts" property stored on the Client object
Check if the client's new failedLoginAttempts > max failed login attempts (from property config), then lock the Client account (client.isLocked = true or client.isEnabled = false)
The client should have a locking or disable feature that would prevent further login
There is NO forgot password for clients, so the only way to unlock/enable a client account is for the administrator or DBA to manage this change.
Add new field in RegistrationModel.cs call PhoneNumber as string type
Add validation rule in RegistrationModelValidator.cs for this field. For now just require rule
Update RegisterAsync method in UserService.cs add phone number field to ApplicationUser class UserPhone collection and set PhoneNumberConfirmed to true