@laosdirg/security v0.2.0
@laosdirg/security
A bunch of helpers for securing javascript applications
import {
hashPassword,
verifyPassword,
generateToken,
splitToken,
verifyToken,
} from '@laosdirg/security.js';
function signUp(request, response) {
// extract provided credentials from request
const { username, passwordPlaintext } = request.body;
// create hashed password
const passwordHash = hashPassword(passwordPlaintext);
// persist user to storage with HASHED password
User.create({ username, passwordHash });
// optionally log in user right away, see below
return logIn(request, response);
}
function logIn(request, response) {
// extract provided credentials from request
const { username, passwordPlaintext } = request.body;
// fetch corresponding user from storage
const { userId, passwordHash } = User.get({ username });
// verify password against stored hash
if (!verifyPassword(passworHash, passwordPlaintext)) {
throw new Error('Unauthorized');
}
// generate fresh token
const { token, selector, verifierHash } = generateToken();
// store with HASHED verifier in database and user reference
Token.create({ selector, verifierHash, userId });
// send unhashed token to user
response.cookie('token', token);
}
function currentUser(request, response) {
// extract token (from cookie, authorization header, etc.)
const { token } = request.cookies;
// split token into selector and plaintxt verifier
const { selector, verifierPlaintext } = splitToken(token);
// use selector to fetch token from storage
const { verifierHash, userId } = Token.get({ selector });
// verify the token based on verifier
if(!verifyToken(verifierHash, verifierPlaintext)) {
throw new Error('Unauthorized');
}
// fetch corresponding user and send as response
const user = User.get({ userId });
response.send(user);
}
Installation
$ npm install @laosdirg/security
API
Passwords
Passwords are be hashed with a salt, and stored with the parameters that generated the password.
hashPassword(plaintext: string)
Returns string
hashed password.
verifyPassword(hash: string, plaintext: string)
Returns boolean
whether verification succeeded.
Tokens (split tokens)
Tokens have many uses: user sessions, password recovery emails, etc., usually they are provided upon presenting some other kind of credentials like a username and password.
However, tokens need to be treated like passwords. If an attacker gains with read-only access to your token database and they are stored as-is, the attacker can steal and use all of your tokens.
Following best practices from paragonie, our approach splits tokens into a username-like and password-like part, and hashes the password-like part. The entire unhashed token can be sent to the user, but the unhashed token should never be stored in your database.
The selector is to prevent leaking timing information.
generateToken()
Returns { token: string, selector: string, verifierHash: string }
split token.
splitToken(token: string)
Returns { selector: string, verifierPlaintext: string}
selector and verifier in dict.
verifyToken(hash: string, plaintext: string)
Returns boolean
whether verification succeeded.
Usefull tools
- helmet for setting various headers in express apps.
- zxcvbn estimating password strength (display this to users during signup)
Additional reading
How to Safely Store Your Users' Passwords in 2016
https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016
Implementing password reset
https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-account-recovery-made-simple
TL;DR:
- DO:
- Make automated password resets optional, and opt-in
- Send a short-lived URL to their email address which contains a random token (generated by a CSPRNG)
- Use a split-token strategy to mitigate side-channels and database leaks
- Allow users to upload a PGP public key, and if it's available, use it encrypt the recovery URL
- DO NOT:
- Email the user their old password
- Change the user's password for them and email them the new one
- Rely on security questions/answers, which most attackers can guess for their targets
Additional security
Cross-Site Scripting (XSS)
Cross-Site Request Forgery
Cross-Site Request Forgery (CSRF/XSRF) is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user's web browser to perform an unwanted action on a trusted site for which the user is currently authenticated. The impact of a successful CSRF attack is limited to the capabilities exposed by the vulnerable application. For example, this attack could result in a transfer of funds, changing a password, or purchasing an item in the user's context. In effect, CSRF attacks are used by an attacker to make a target system perform a function via the target's browser without knowledge of the target user, at least until the unauthorized transaction has been committed.
Defense
It is imperative that no XSS vulnerabilities are present to ensure that CSRF defenses can't be circumvented.
The synchronizer token pattern requires the generating of random "challenge" tokens that are associated with the user's current session. These challenge tokens are then inserted within the HTML forms and links associated with sensitive server-side operations. When the user wishes to invoke these sensitive operations, the HTTP request should include this challenge token. It is then the responsibility of the server application to verify the existence and correctness of this token. By including a challenge token with each request, the developer has a strong control to verify that the user actually intended to submit the desired requests. Inclusion of a required security token in HTTP requests associated with sensitive business functions helps mitigate CSRF attacks as successful exploitation assumes the attacker knows the randomly generated token for the target victim's session. This is analogous to the attacker being able to guess the target victim's session identifier.