Node Authentication with Express and Knex
By: Karl Matthes
Tags: Knex, Node, Flatiron School, Authentication, Expressjs

Before setting up authentication in Node, I only knew how to do this in Ruby and Rails. Fortunately, the process is very similar, and just needs a little bit more configuration along the way. Now assuming you already have Node installed, here’s my checklist for getting a bare bones Node authentication set-up.

In a new folder where you want this project to reside, start with npm init or npm init -y to create your package.json file. Next, we’ll install everything we’re going to be using along the way. In no particular order, run:

npm install express

npm install cors

npm install bcrypt

npm install knex

npm install pg

npm install body-parser

npm install jsonwebtoken

A few side notes: You may also need to install Knex and PostgreSQL globally; just add a -g to the line, nodemon is useful when working with Node projects and will save you from needing to frequently restart your server, and if you are intending to push this to GitHub, add node_modules/ to your .gitignore file.

Now, let’s make a basic HTTP server. Start with touch index.js and write in the following:

const express = require("express")

const app = express() app.get("/", (request, response, next) => {

response. sendStatus(200)

}) app.listen(9000)

Run the server with node index.js and then check out your http://localhost:9000 and make sure there is a “OK” on the screen. For this example, Express is being used to handle routing. So, it is required, invoked, and kept in the “app” variable. With app.get , a GET request route is being set-up, with the first argument being the address that will be listening for the request, and the second argument being a callback function. In that callback, “request” will be the HTTP request and its payload (headers, body, etc.), “response” will be the HTTP response, and “next” will direct the routing to continue to the next valid route (useful for logging and error handling, but I’m not going to be using it too much in this article). Finally, app.listen is telling the server to listen for requests on port 9000.

Next, we’ll set-up everything with Knex, which is a SQL query builder. I’m going to run through a few steps and then explain them afterward, so I recommend following the steps as is until that point, and then I’ll unpackage what they’re doing. Back in the terminal, run knex init and createdb myexampledatabase . The knex init will create knexfile.js and the createdb will create a PostgeSQL database. You can check on the database by using psql myexampledatabase , but I’m not familiar enough with the PostgreSQL or SQL commands and queries needed after that point to write about them, so you’re on your own on that front! In the knexfile.js, we can replace all the contents with the following:

module.exports = {

development: {

client: "pg",

connection: "postgres:///myexampledatabase

}

}

Return to the terminal, and run knex migrate:make create-users-table to create a new “migrations” folder and our migration file, which will be labeled with a time code and the phrase “create-users-table”. I’m sticking to the bare bones, so we’ll just have a username and password for each user in the database. In migration file, add the following:

exports.up = function(knex){

return knex.schema.createTable("user", table => {

table.increments()

table.string("username")

table.string("password_digest")

})

} exports.down = function(knex){

return knex.schema.dropTableIfExists("user")

}

Then run knex migrate:latest to apply this migration.

Great, now let’s go back over these last few steps. The knexfile is like a package.json or Gemfile and exists to describe what type of database we’re using (pg, or PostgreSQL) and where it is located. The migration file should look familiar if you’ve used Ruby on Rails, and is telling the database to create a new record called “user” with usernames and passwords, and to drop them if they already exist.

To get this into the index.js file and work with the routing, we need one intermediate file, which we’ll call database_connection.js In it, write:

const environment = "development"

const config = require("./knexfile")[environment]

const knex = require("knex")(config) module.exports = knex

Simple file: direct the required Knex to the use the “development” configuration from the knexfile, and export it.

Back in index.js, we’ll want to add the following:

const database = require("./database_connection")

const cors = require("cors")

const bodyParser = require("body-parser") app.use(cors())

app.use(bodyParser.json())

And now we can add a POST route for new users. The order you place the routes in does matter if you are doing logging or making multiple things trigger off each route, but for our purposes, just make sure they are after the require statements and before the app.listen line.

app.post("/users", (request, response, next) => {

database("user").insert({

username: request.body.username,

password_digest: request.body.password

})

.returning(["id", "username"])

.then(users => {

response.json(users[0])

})

.catch(error => next(error))

})

To summarize, when a POST request is sent to /users route, it will take the username and password out of the request, and insert it into the “user” database in our myexampledatabase. It will also produce a response to help show that the request was successful. Finally, the .catch line is there for error handling.

But we not going to keep this POST route because we need to add bcrypt to this route. I’m including this basic POST to serve as a boilerplate or for simple testing, but we’re going to change it to the following with brcypt:

const bcrypt = require ("bcrypt")

And then replace the prior POST route with:

app.post("/users", (request, response, next) => {

bcrypt.hash(request.body.password, 10)

.then(hashedPassword => {

return database("user").insert({

username: request.body.username,

password_digest: hashedPassword

})

.returning(["id", "username"])

.then(users => {

response.json(users[0])

})

.catch(error => next(error))

})

})

Not terribly different. The only real change is that we’re hashing the password with bcrypt ten times, and then inserting it to the database.

I skipped seeding anything, and we now have a POST route, so it would be nice to pair it with its GET counterpart. Simplest version to return all users is:

app.get("/users", (request, response, next) => {

database("user")

.then(users => {

response.json(users)

})

})

Finally, the actual authentication with the login route. First, a couple more require statements:

const jwt = require("jsonwebtoken")

const SECRET = "any_secret_you_want_to_use"

I’m just going to post this as a giant code block, and then explain it:

app.post("/login", (request, response, next) => {

database("user")

.where({username: request.body.username})

.first()

.then(user => {

if(!user){

response.status(401).json({

error: "No user by that name"

})

}else{

return bcrypt

.compare(request.body.password, user.password_digest)

.then(isAuthenticated => {

if(!isAuthenticated){

response.status(401).json({

error: "Unauthorized Access!"

})

}else{

return jwt.sign(user, SECRET, (error, token) => {

response.status(200).json({token})

})

}

})

}

})

})

So, this starts when the user hits “Log-in”, sends the POST request containing their username and password. The first check being done is with the username: does it exist in the database? If the user does not exist already, return a message saying so; if it does, continue. The second check uses bcrypt to compare the password the user provided at login against the hashed password in the database. If they don’t match, return a message that says this may be an unauthorized login attempt. If they do, JWT creates and returns a token containing the found user.

Having the token is nice and all, but we will likely need to get some information out of it. We can do this by sending the token to this route:

app.get("/verify", (request, response, next) => {

const token = request.headers.authorization.split(" ")[1]

jwt.verify(token, SECRET, (error, decodedToken) => {

if(error){

response.status(401).json({

message: "Unauthorized Access!"

})

}else{

response.status(200).json({

id: decodedToken.id,

username: decodedToken.username

})

}

})

})

This is also assumes that you’re sending the token as a GET request, and inside an “authorization” header. This route is simply using JWT to decode and return the contents.

There’s certainly a lot more details, and ton of extra things you could bring in with Node, but this should be a good starting framework for authentication. From here, you should be able to search for or Google missing pieces and make this a better fit to your own project.