Authentication with React, Redux, JSON Web Tokens, Node, & Sequelize : Part 1 of 4

Katie J. Duane

I get that the above, er, conglomeration of logos (and subsequent technologies) that I’m using is a tad specific; but I also recognize I can’t have been the only one who wanted to use them all together… but when I did set about to use all these technologies for auth, I learned that resources for this conglomeration were indeed scant. So, then, I was inspired to make some!

When I finished my coding bootcamp, I felt like I could accurately identify my personal strengths (React!) and weaknesses(SQL), as well as any holes in my knowledge. We learned A LOT, but 16 weeks is, well, never going to be sufficient to cover it all (that would require eternity). One such hole was authentication with SPAs (single page applications); we’d covered the essentials; but I hadn’t been the one to implement authentication during our final project, so I didn’t quite understand what all the pieces were, and what they did or why. I despised the idea of marching down a new career path with such an expansive vacancy in my brain, and set out to fill it; even if only partially. Authentication, I quickly realized, is one of the trickier and more involved aspects of web development.

To boot, I thought it would be “fun” to learn Sequelize, an ORM (an ‘object relational mapping’ technique for SQL databases with Node.js). I found that it was easier for me think in ‘object shapes’ than it was for me to think in SQL queries. What I failed to realize is that learning an ORM, like for real learning one, to use in application, is a very non-trivial task. I spent countless hours poring over the docs. While I totally recommend this technique for deeper understanding, if you simply want to learn a few things to streamline authentication, then this article may well be for you, and you can read the docs later.

Pre-reqs: This article is best suited for those with a foundational understanding of front and backend development who need some guidance in setting up authentication with JWT and Redux. I’m not going to go through every step of setting up your React App or Express server, nor will I demonstrate how to make an HTML form or a POST request to your database. If you’re reading this just for the Redux/JWT sections and not using Sequelize, that’s totally fine, but an understanding of MVC frameworks and writing SQL queries is definitely required. A foundational understanding of promises and async/await is also essential. As an aside: I’m using code from an existing (and still in-progress) application I am building. Last: I link to several articles and docs throughout; I’d recommend clicking and reading to learn more!

PART 1a: Creating your User model with bcrypt

const Sequelize = require('sequelize');
const sequelize = require('../util/database');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const User = sequelize.define('user', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
firstname:{
type: Sequelize.STRING,
allowNull: false
},
lastname: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false
},
hash: {
type: Sequelize.STRING,
allowNull: false
}
});

In the above code block, we are only creating the User model (.define is a built in method). If you’re new to Sequelize, the first two lines of code require not only Sequelize itself, but a database config I’ve created with the dialect (ie: mysql, postgresql) and the credentials needed to access the database. I’ve pasted my DB config below, but I’d highly recommend looking at the docs to ensure you’re doing this to best suit your application’s needs. I also require the node module ‘bcrypt’ and create a variable called ‘saltRounds’. I’ll get to that later, but be sure to include both!

Your user model may also need additional data fields, but this is a basic set of requirements. I’ve assigned data types to each field (ie: Sequelize.STRING) as well as other parameters (like, whether or not the field can be null, if it is a primary key, etc) using Sequelize syntax. There are many other data types and many other parameters you can specify in your model, but I’m keeping it simple here! Also note: there are and will be no SQL queries. Sequelize takes your commands (we’ll see those later), interprets them, and writes the queries for you. I thought this would make SQL easier. I was wrong. It just made it different. (If you’re curious about what it’s doing under the hood, definitely log the queries to the console when you run them!)

//db configconst Sequelize = require('sequelize');const sequelize = new Sequelize('yourDBname', 'yourDBusername', 'yourDBpassword', {
dialect: 'postgresql', //or whatever dialect you're using
host: 'localhost'
})
module.exports = sequelize;

The above code block, again, refers to my database config. Yours might need to look a bit (or very) different, but however it looks, it’s required when creating a model so that you can access your DB using Sequelize anytime you need to insert or retrieve data.

You also can’t use Sequelize unless you have some basic requirements set up in your app.js →

const sequelize = require("./util/database");//check DB connection
sequelize
.authenticate()
.then(() => {
console.log("connection was successful!");
})
.catch(err => {
console.log("unable to connect to DB", err);
});
// sync DB
sequelize
.sync()
.then(result => console.log(result)
.catch(error => console.log(error)

The above code is the bare minimum and may not quite fit your application’s needs (there’s actually a LOT more in my app.js, but I cut it here for simplicity’s sake). If you’re going to look at the Sequelize docs for anything, do it for this: getting started.

Now, we need to include a little logic in our user model to run any time we create or validate a user.

Part 1b: Encryption with bcrypt

// hash password with bcrypt
User.beforeCreate((user, options) => {
return bcrypt.hash(user.hash, saltRounds)
.then(hash => {
user.hash = hash;
})
.catch(err => {
console.log(err)
throw new Error();
});
});

User.beforeCreate is exactly what it sounds like. It’s a Sequelize method that allows you to do something to the data you’re sending in the request body before it’s actually stored in the database. In this case, we don’t want to store the user’s actual password, so we need to encrypt it first. bcrypt is a very handy node module that does that crazy encryption math for us. In essence, it takes the password and hashes it, using something called saltRounds (you can call it anything, but this is convention). The saltRounds determine the cost factor; or, how long it takes computationally to encrypt the password, and how deeply encrypted it becomes. Too high means it takes too long to load, and your user never revisits your site; too short, and it can be easy for math-genius-attackers to decrypt your poor user’s password (and then they never return to your site). In this case, to be safe but not slow, we use 10.

But! If we’re using bcrypt to hash the password before it’s stored in the DB, then we need a way to compare that hash with passwords sent during future login attempts:

// prototype method for all users to check password
User.prototype.authenticate = async function (value, callback) {
await bcrypt.compare(value, this.hash, function(err, same){
if (err){
console.log(err)
callback(err)
}else{
console.log('authenticate', err, same)
callback(err, same)
}
})
}

Here, we’re creating a prototype method called ‘authenticate’ for all users that we’re going to run from within the login function. Because encryption can be slow, we’re using async/await to ensure we get the data we need before proceeding. bcrypt.compare is what allows us to compare the hash in the DB to whatever the user has typed into the login form. compare requires that we send a value (the password) and a callback to handle the results. It’ll make more sense when we look at login logic later.

Part 1c: Sign up logic (creating your user controller)

Now we need the logic that is going to allow us to get the data we need out of the request body coming in from frontend (via your form) to both create new users and also validate existing ones (this is where we’ll send back a glorious JSON web token!). First, let’s create a new user (ie: sign them up!)

const jwt = require("jsonwebtoken");const User = require("../models/user");
const config = require("../config");
// sign up
module.exports.postSignUp = (req, res, next) => {
const firstname = req.body.firstname;
const lastname = req.body.lastname;
const email = req.body.email.toLowerCase();
const password = req.body.password;
User.findOne({
where: {
email: email
}
})
.then(user => {
if (!user) {
User.create({
firstname: firstname,
lastname: lastname,
email: email,
hash: password
})
.then(response => {
res.json(response);
})
.catch(err => console.log(err));
} else {
res.json({
msg: "User already exists!"
});
}
})
.catch(err => console.log(err));
};

BE SURE you require your config file with your jwt secret (see below for example), and JWT (we’ll get there soon!). You’ll also need to require your User model so you can work with it here!

let config = {
secret : 'whateverYouWantYourSecretToBeThisIsNotActuallyMine'
}
module.exports = config;

In the above function, we first try to findOne (this is a built in Sequelize method) User whose email in the DB matches the one we’re sending in the request body. If there’s one that matches, we return (res.json) a message to the frontend indicating the user already exists (it’s up to you how you want to handle this and display appropriate error messages on your frontend). Otherwise, it creates a new user. Remember, in our user model we have the ‘beforeCreate’ method that hashes the password we send back. This is the easy part!

Now, we need to log users in when they return to the site. Here, we have to check a few more things: Do the passwords match? Does the email match? And if it’s a go, how do we send back a JSON web token that the frontend can use to keep the user logged in?

Part 1d: Sign in logic (another user controller)

// sign in
module.exports.postSignIn = (req, res, next) => {
const email = req.body.email.toLowerCase();
const password = req.body.password;
User.findOne({
where: {
email: email
}
})
.then(function(user, err) {
if (err) {
console.log(err);
res.status(500).json({
error: "Internal error please try again",
auth: false
});
} else if (!user) {
res.status(401).json({
error: "User email does not exist",
auth: false
});
} else {
user.authenticate(password, function(err, same) {
if (err) {
res.status(500).json({
error: "Internal error please try again",
auth: false
});
} else if (!same) {
res.status(401).json({
error: "That password is incorrect",
auth: false
});
} else {
const token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 36000
});
res.json({
token: token,
expiresIn: 36000,
msg: "Welcome!",
user: user,
error: false,
auth: true
});
}
});
}
})
.catch(err => console.log(err));
};

Let’s examine this bit by bit.

  1. We retrieve the email and password from the request body and search for a user (User.findOne) using the email as the parameter. We handle a server error, as well as the case where the email doesn’t match anything in the DB. Again, it’s up to you how your frontend handles these error messages; the important thing here is that the backend sends them!
  2. We execute our User prototype method (‘authenticate’), including both the password from the request body as well as a callback function. This is the function passed to ‘authenticate’ that handles whether or not they’re the ‘same’, or if there’s a mismatch or other error. We handle the case of a server error, and a mismatch, sending to the frontend the appropriate error messages. If passwords DO match…
  3. We assign a JWT (JSON web token!) jwt.sign is a built-in method that creates a JSON web token for your user upon login. You must pass in an identifying feature (I used the User ID), your JWT secret, and expiration time in milliseconds.
  4. We return (res.json) specific data to the frontend. You can send whatever you want, but you NEED to send the token. I send other data that I use on the frontend (like auth: true) for various functionality (and you will too, when we implement Redux), but for now, returning the token is essential.

If you want to test this functionality (and I highly recommend that you do) before we set up the frontend with Redux, make a simple form within a React component that can post data to your database. Don’t worry about actual authentication, or Redux, just post sign up/sign in data to ensure your functionality on the backend works before we introduce the complexity of Redux, writing your own headers, persisting auth state, and creating middleware (whew!).

A few things to consider/try: Be sure your routes files on the backend are connected to the necessary controllers.

router.post(“/signup”, userController.postSignUp);router.post(“/signin”, userController.postSignIn);

console.log the sign up and sign in functions on the backend so you can actually see the SQL queries that Sequelize is generating for you. Additionally, console.log the response on the frontend so you can see the token arrive in all its glory. It really is a beautiful thing.

In the next article, we’ll cover making sign up requests using Redux, and how to properly connect your components to Redux for authentication! ‘Sign In’ (along with various checkToken functions and middleware) will be tackled in the further installments of this riveting, suspense-filled series! ;) ❤

In conclusion: your files should all look somewhat like this, differing of course, with your application’s specific needs →

Until next time! ❤ ❤ ❤

Katie J. Duane

Written by

i ❤ to write about art, tech, learning, and exploration

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade