5 minutes to JWT Authentication in Javascript
Express JWT Middleware, NodeJS Crypto & React
Authentication is a complex and mostly divided topic with some preferring Stateful Session Cookies approach and others choosing Stateless JWTs. I'm one of the latter as I like how quickly and effective implementing JWT Auth is.
Whenever we create an application we need to be aware of some important security aspects:
1. Passwords
Passwords should never be stored in plain text in the database as it can prove to be a massive vulnerability.
Assume an attacker (hacker) gained access to our database which is already a huge security issue and our application has therefore been compromised. If we stored login information in plain text the attacker can reuse the email & password combos to access other common websites the user might use like Google or even Online Banking.
Yes most of this platforms require 2FA or other extra steps to authenticate but that's not a reason to knowingly risk exposing client credentials.
Fortunately the solution is rather simple and that is storing a hash in the database instead of the plain password.
A hash is the result of cryptographic function that receives an input and always returns a unique fixed size (256 bits for SHA256) for that particular input.
Generate SHA256
const { createHash } = require("crypto");
const hexHashedValue = createHash("sha256").update("Some string").digest().toString("hex");
2. Authentication (JWT)
JWT (JSON Web Tokens) are a globally standardised way of storing user credentials and passing them between 2 parties (frontend client & backend server) in a secure manner.
Basically a JWT is a string which should be generated on the backend and stored in the frontend to be sent with every request.
But how do we generate this token and where do we store in on the client?
Let's see how we can use JWTs to authenticate users in an Express application:
I'll be using express-jwt middleware library as it's very simple and straightforward, to note there are other maybe more robust packages such as passport.
Generate JWT
We first have to create our sample project with the following commands:
mkdir express-jwt-example
cd express-jwt-example
npm init -y
npm install express express-jwt
After that just create an index.js
file and add a start script to package.json like this
"start": "node index"
We need to expose 2 endpoints, one for users to register and another for login.
const express = require("express");
const jwt = require("express-jwt");
const { sign } = require("jsonwebtoken");
const { createHash } = require("crypto");
const app = express();
const jwtSecretKey = "REPLACE_THIS_WITH_SECRET_KEY";
const users = [];
app.use(express.json());
// add JWT middleware to check for token validity unless route is for login or register
app.use(
jwt({
secret: jwtSecretKey,
algorithms: ["HS256"],
requestProperty: "auth",
}).unless({
path: ["/users/login", "/users/register"],
})
);
app.post("/users/login", (req, res) => {});
app.post("/users/register", (req, res) => {});
app.listen(8080, () => {
console.log("Server started");
});
Register
Here we have to generate the hashed password and insert it in the database with the provided email.
app.post("/users/register", (req, res) => {
const { email, password } = req.body;
// generate hash from plain password
const hashedPassword = createHash("sha256").update(password).digest().toString("hex");
// find user from db with matching email & hashedPassword
let user = users.find((u) => u.email === email && u.password === hashedPassword);
if (user) {
res.status(401).send({
message: "Email already exists.",
user,
});
} else {
// change to insert in DB not session variable
users.push({ email, password: hashedPassword });
res.status(200).send({
message: "Account created.",
});
}
});
Login
Similar to register we need the hash value of the password and use it to check if the email & hashedPassword pair exist in the database. Using the user object we sign the JWT token and return it in the exposed Authorization response header.
app.post("/users/login", (req, res) => {
const { email, password } = req.body;
// generate hash from plain password
const hashedPassword = createHash("sha256").update(password).digest().toString("hex");
// find user from db with matching email & hashedPassword
const user = users.find((u) => u.email === email && u.password === hashedPassword);
if (user) {
// generate JWT token with user credentials as encrypted payload
const token = sign({ user }, jwtSecretKey, {
algorithm: "HS256",
});
// expose Authorization header to return to client
res.header("Access-Control-Expose-Headers", "Authorization");
res.header("Authorization", token);
res.status(200).send({
message: "Login successful.",
user: { email },
});
} else {
res.status(401).send({
message: "Wrong email or password.",
});
}
});
Store JWT
There are 2 common ways to keep JWTs persisted in the client side:
- Cookies
- Local Storage
Both come with security issues like XSS, CSRF or advanced XST therefore neither of those storage options is entirely secure.
I prefer storing JWTs in cookies as that may be just a slight bit more secure than Local Storage IMOP.
Let's presume our client is a SPA and more precisely a React powered application and the client often makes requests to the API using an axios client.
Let's create a custom axios client class which we'll call request.js.
We'll be using react-cookie as it provides a simple hook API to manipulate cookies.
import axios from "axios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
const client = (() => {
return axios.create({
baseURL: process.env.REACT_APP_API_URL
});
})();
const request = async function (
options,
store,
) {
const onSuccess = function (response) {
// check for Authorization header and if exists set cookie
if (response.headers.authorization) {
cookies.set("token", "Bearer " + response.headers.authorization);
}
return response.data;
};
const onError = function (error) {
return Promise.reject(error.response || error.message);
};
if (cookies.get("token")) {
// add cookie to header to be sent to API on every new request after login
options.headers = {
...options.headers,
Authorization: cookies.get("token"),
};
}
return client(options).then(onSuccess).catch(onError);
};
export default request;
Wrapping Up
Security is a tricky domain and authentication is often even trickier since protecting user data is the most important goal of an application.
If you want to check the code here is the CodeSandbox.
I hope you enjoyed this short showcase of JWT Authentication in Javascript and would love if you give it a ๐ฆ!