excellarateinc / voyage-api-dotnet

Enterprise grade C# .NET Web Services API implementing industry standard best practices
Apache License 2.0
16 stars 14 forks source link

Forgot Username / Password Support #57

Open maniphanh opened 7 years ago

maniphanh commented 7 years ago

Workflow

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.

  1. 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.
  2. 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
  3. 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).
  4. 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.
  5. 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.
  6. 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.
  7. 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
  8. 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"
  9. 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
  10. 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)
  11. 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

References

raymer commented 6 years ago

@tmichalski This is complete in the OAuth website (except the security question piece was never built). Thoughts on whether to either create a new issue for an API version / revisit to add the security questions piece? Most of this is done though.