require the package and optionally pass it a config object, or rely on sensible defaults.
When executed, the package returns semi-configured Express middleware. (Most of the functions returned need to be executed to produce the middleware.)
const config = {}; //see below for defaults
const ValidateAuth = require('validate-auth');
const {
validateEmail,
validateNewPassword,
allowNewPassphrases,
allowExistingPassphrases,
sendPasswordRequirements
} = ValidateAuth(config);
const express = require('express');
cosnt app = express();
// send the password requirements to the client as JSON:
app.get('/password-requirements', sendPasswordRequirements);
// Validate email and password or passphrase, then allow sign-up
app.post('/sign-up', validateEmail(), validateNewPassword(), allowNewPassphrases(), signUp);
// Validate email and allow passphrase, then allow
app.post('/log-in', validateEmail(), allowExistingPassphrases(), logIn);This config object is passed to the main module. The following config object shows default values:
const config = {
emailLocation: 'email',
passwordLocation: 'password',
passphraseLocation: 'passphrase'
transformEmailLowerCase: true,
password: {
minLength: 8, // minimum 8 characters
needMixed: true, // need mixed case letters
needSymbol: true, // need non-alphanumeric
needNumber: true, // need number
needAlpha: true, // need alphabetical
override: false // 'true' removes all password requirements, sets minLength=1
},
passphrase: {
minWords: 9 // minimum number of words
}
};- Gives the name of the property on
req.bodyused to hold the email address (defaults to'email', pointing toreq.body.email)
- Gives the name of the property on
req.bodyused to hold the password (defaults to'password', pointing toreq.body.password)
- Gives the name of the property on
req.bodyused to hold the passphrase (defaults to'passphrase', pointing toreq.body.passphrase)
- Establishes password validation requirements, with sensible defaults and the ability to override them all:
config.password.minLength: 8- Minimum password length? (default is8)config.password.needMixed: true- Mixed typecase required? (default istrue)config.password.needSymbol: true- Non-alphanumeric symbol required? (default istrue)config.password.needNumber: true- Numeral required (default istrue)config.password.needAlpha: true- Letter (Latin alphabet) required? (default istrue; this is redundant with the defaultneedMixed: trueshown above)config.password.override: false- Overrides all password requirements, only requiring that the password be a single character long. (Only use for testing and demo environments!!)
Validates an email address, trimming it for whitespace. Transforms it to lower case, unless transformEmailLowerCase is specifically set to false in the module config object.
validateEmailmust be called as a function, which returns the middleware:
// default config:
app.post('/sign-up', validateEmail(), signUp);
// or custom config:
app.post('/sign-up', validateEmail(config), signUp);The config object has the following defaults:
const config = {
invalidEmailMessage: 'Invalid email address',
errorStatus: 400
}invalidEmailMessageis sent to the client- errorStatus is the response status (
400is 'Bad Request')
Validates a password, ensuring it meets all rules set in the main module's config object.
validateEmailmust be called as a function, which returns the middleware:
// default config:
app.post('/sign-up', validateNewPassword(), signUp);
// or custom config:
app.post('/sign-up', validateNewPassword(config), signUp);Important Caveats:
-
Validate new passwords only. Do not validate passwords for login, or the user may not be able to log in if their existing password does not meet the current requirements—hence
Newin the name of the function. -
When validating passwords and also allowing passphrases,
validateNewPasswordneeds to be passed before theallownewPassphrasesorallowExistingPassphrasesfunction. PassingvalidateNewPasswordas middleware afterallow***Passphraseswill throw an error.- Why?
validateNewPasswordworks on thereq.body.passwordobject, expecting it to be an actual password.allow***Passphrasestests whether there is areq.body.passwordor areq.body.passphrase, and if an adequate passphrase is provided, normalizes it (to lower case, removing duplicate words), and setsreq.body.password. Such a password is a long list of words, and is not subject to the password rules, but usingvalidateNewPasswordafterallow***Passphraseswould check it to password requirements, and often reject it. An error is intentionally thrown to avoid this setup.
The
configobject has the following defaults: - Why?
const config = {
invalidPasswordMessage: `Password doesn't meet requirements`,
errorStatus: 400
}invalidPasswordMessageis sent to the client- errorStatus is the response status (
400is 'Bad Request')
Normalizes a passphrase, and sets req.body.password to the normalized passphrase. Your application can proceed using only req.body.password and not worry about whether the user had specified a req.body.password or req.body.passphrase.
allowNew... vs. allowExisting...: Use allowNewPassphrases when setting a new passphrase, as it enforces the minimum length requirements. Use allowExistingPassphrases when verifying a passphrase, as it does not enforce minimum length requirements, in case they've changed.
-
Otherwise, allowing a user to set a 5-word passphrase and then increasing the minimum length policy to 8 words would prevent them from verifying their 5-word passphrase and logging in.
-
allowNewPassphrasesandallowExistingPassphrasesmust be called as a function, which returns the middleware:
// default config:
app.post('/sign-up', validateNewPassword(), allowNewPassphrases(), signUp);
app.post('/log-in', validateNewPassword(), allowExistingPassphrases(), logIn);
// or custom config:
app.post('/sign-up', validateNewPassword(), allowNewPassphrases(config), signUp);
app.post('/log-in', validateNewPassword(), allowExistingPassphrases(config), logIn);When setting a passphrase, validateNewPassword must be passed in before the allowNewPassphrases or allowExistingPassphrases. See explanation above under validateNewPassword.
Transformations of the passphrase are not configurable, as they must be the same in order for the user to log in again with the same passphrase. They include the following:
- Passphrase is transformed to all lower case, so user doesn't need to remember capitalization
- All punctuation and other non-alphanumeric characters are removed; intention is for the password to be a sequence of words (and possibly numbers) only. Note that characters like
'é'are treated as non-alphanumeric and are removed - All repeated words are removed, leaving only the first instance, to avoid passphrases like
'ab ab ab ab ab ab' - All single-character words are removed, to avoid passphrases like
a b c d e f g
The resulting passphrase is only used if it has the minimum number of words set by passphrase.minWords in the main module config object, after duplicate words were removed. (Note that users could be confused if they use the required 6-word passphrase and duplicate or single-character words are removed, resulting in an error based on passphrase length)
- The default value of
passphrase.minWords = 6gives 54 bits of entropy, which is strong based on an online recommendation in 2021. This Science Direct publication suggests that in English, each word has ~10 bits of entropy.
The resulting passphrase is placed into req.body.password (or the alternative path configured in the main module config object), so that your application can not worry about whether a password or passphrase was used, and just refer to req.body.password.
The allowNewPassphrases and allowExistingPassphrases config objects are identical and have the following defaults:
const config = {
// return an error if there is neither a password or passphrase
allowNeither: false,
haveNeitherMessage: `Need req.body.password or req.body.passphrase`,
// (having both a password and passphrase always returns an error)
haveBothMessage: `Need only one of req.body.password or req.body.passphrase`,
// Only for this one, you can use ${minWords} in a non-template string:
shortPassphraseMessage: 'Normalized passphrase needs ${minWords} unique words > 1 character',
errorStatus: 400 // 400 is 'Bad Request'.
}allowNeither: falsereturns an error to the client if there is neither apasswordorpassphraseon the request body; usetrueto turn this off and permit such requestshaveNeitherMessageis the error message to send to the client when neither a password nor passphrase is providedhaveBothMessageis the error message to send to the client when both a password and passphrase (can't turn this one off)shortPassphraseMessageis the error message to send when the normalized passphrase is not long enough. Only in this error message, you can include'${minWords}'in a non-template string to include the number of words required- errorStatus is the response status (
400is 'Bad Request')
Sends an object in JSON format with the chosen password rules, for enforcement on the client side before making the request.
- Note that
sendPasswordRequirementsis a normal Express middleware function, and does not need to be executed inline to return the middleware.
const {sendPasswordRequirements} = ValidateAuth({password:{override: true}});
app.get('/password-requirements', sendPasswordRequirements);
// when 'override' is 'true', responds to the client with the following in JSON:
// {
// minLength: 1,
// needMixed: false,
// needSymbol: false,
// needNumber: false,
// needAlpha: false
// }const {sendPasswordRequirements} = ValidateAuth();
app.get('/password-requirements', sendPasswordRequirements);
// When 'override' is 'false', or not specified, responds to
// the client with the following in JSON:
// {
// minLength: 8,
// needMixed: true,
// needSymbol: true,
// needNumber: true,
// needAlpha: true
// }