Bootcamp
Search…
3.5.4: Session Hashing

Introduction

We need to make our original implementation of cookies more secure, because anyone can open the Chrome Dev Tools Application tab and type in a cookie value.
We'll use the idea of a "salt", which we are going to use as a system-wide secret word. If we combine it with information we set in a separate cookie, we can create a hashed value in the cookie instead of the string "loggedIn" and have that value be verifiable when our server checks if the cookie is authentic. Salts can also be used with passwords to help prevent brute-force password attacks.

Session Hashing Implementation

Summary

  1. 1.
    After the user logs in, use the user ID and a secret salt to generate a hashed cookie value. Store both the hashed cookie value and user ID on the user's browser.
  2. 2.
    When the user visits the site again, verify the hashed cookie value by regenerating the hashed cookie value server-side with the user ID cookie and the secret salt.
After successful login, we generate a hashed cookie value using a combination of user ID and salt, and send that value to the client in a response cookie.
import jsSha from "jssha";
​
// the SALT is a constant value.
// In practice we would not want to store this "secret value" in plain text in our code.
// We will learn methods later in Coding Bootcamp to obfuscate this value in our code.
const SALT = "bananas are delicious";
​
// ... route declaration, user record retrieval, and password hashing here
​
// if hashed password doesn't match, return
if (user.password !== hashedPassword) {
response.send("login failed!");
return;
}
​
// create new SHA object
const shaObj = new jsSha("SHA-512", "TEXT", { encoding: "UTF8" });
// create an unhashed cookie string based on user ID and salt
const unhashedCookieString = `${user.id}-${SALT}`;
// generate a hashed cookie string using SHA object
shaObj.update(unhashedCookieString);
const hashedCookieString = shaObj.getHash("HEX");
// set the loggedInHash and userId cookies in the response
response.cookie("loggedInHash", hashedCookieString);
response.cookie("userId", user.id);
// end the request-response cycle
response.send("logged in!");
When the client visits pages that require authentication, we regenerate the hashed cookie value and verify it against the request cookie value. If the values match, the client is authenticated.
import jsSha from "jssha";
​
// the SALT is a constant value.
// In practice we would not want to store this "secret value" in plain text in our code.
// We will learn methods later in Coding Bootcamp to obfuscate this value in our code.
const SALT = "bananas are delicious";
​
// ... route declaration here
​
// extract loggedInHash and userId from request cookies
const { loggedInHash, userId } = request.cookies;
// create new SHA object
const shaObj = new jsSha("SHA-512", "TEXT", { encoding: "UTF8" });
// reconstruct the hashed cookie string
const unhashedCookieString = `${userId}-${SALT}`;
shaObj.update(unhashedCookieString);
const hashedCookieString = shaObj.getHash("HEX");
​
// verify if the generated hashed cookie string matches the request cookie value.
// if hashed value doesn't match, return 403.
if (hashedCookieString !== request.cookies.loggedInHash) {
response.status(403).send("please login!");
return;
}
​
// hashed values match and cookie is authentic.
response.send("logged in!");

Environment Variables for Secrets

In practice we would never put secret strings like salts in JavaScript. In general it's considered bad practice to keep secrets inside a codebase, because anyone that accesses the codebase will access the secret.
The alternative is to use environment variables, which are a Unix concept that sets values on individual computers, where the computer is the "environment". Our code can then read those environment variables when our code files load.
Depending on the scale of the organization, there could be an entire set of infrastructure dedicated to managing these secret environment variables. Here are some common implementations. In a basic implementation, our system is already more secure if the value is in an environment or file on our computers that is not stored in the codebase.

Set Environment Variables on Command Line

Set an environment variable with the export command on the command line. Env vars are named in SCREAM_CASE by convention.
export MY_ENV_VAR='hello'

Read Environment Variables in JavaScript

Read environment variables with process.env built-in object in JavaScript. process.env stores the environment variables for the currently-running process.
const myEnvVar = process.env["MY_ENV_VAR"];

Set Environment Variables on Shell Startup

Using export on the command line will set the environment variable only for the current terminal session. This means that if we were to close and reopen our terminal, or open a terminal window somewhere else, the environment would be reset.
To set the env var in all terminal sessions by default, add the export command with the env var definition into our ~/.bash_profile or ~/.zshenv files, depending on whether we are using Bash or Zsh respectively. These files are executed when we open a new terminal window. They may not exist by default, so if they don't exist already, feel free to create them.
Read more about env vars here, for more information on why we use ~/.bash_profile instead of ~/.bashrc or ~/.profile in Bash, see here. Past students have found this video helpful in describing .bashrc, .profile, and .bash_profile. For more information on why we use ~/.zshenv in Zsh, see here.

Auto-managing Environment Variables with dotenv

dotenv is an NPM library to manage env vars locally.