Building a PostgreSQL API in JavaScript with Express and Sequelize (Part 3): token-based authorization

Photo credit: Buena Vista Pictures

Background: a token of my extreme

9. Configure auth key

  • The header (“shipping label”): a cryptographic hashing algorithm used to scramble and unscramble our signature, to make sure it reaches its destination securely and safely
  • The signature (“postage”): the scramble that verifies the parcel was properly sent and accounted for
  • The payload: the “stuff” inside the “parcel”
module.exports = {
secret: process.env.AUTH_SECRET
};

NEVER EVER reference auth secrets directly!

PORT = 3001
DB_HOST = "127.0.0.1"
DB_USERNAME = "postgres"
DB_PASSWORD = "postgres"
AUTH_SECRET = "1veG0tAS3cret"

10. Add password column to User with a migration

'use strict';module.exports = {  up: async (queryInterface, Sequelize) => {
return queryInterface.addColumn( "Users", "password", Sequelize.STRING );
},
down: async (queryInterface, Sequelize) => {
return queryInterface.removeColumn( "Users", "password" );
}
};
'use strict';const { Model } = require( 'sequelize' );
module.exports = ( sequelize, DataTypes ) => {
class User extends Model {
static associate( models ) { ... }
};
User.init( {
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
sequelize,
modelName: 'User',
} );
return User;
};

11. Create middleware

├── node_modules
├── app
│ └── config
│ └── controllers
│ └── middleware
│ │ └── index.js
│ │ └── verifySignup.js
│ │ └── authorizeJwt.js
│ └── migrations
│ └── models
│ └── routes
│ └── seeders
├── .env
├── .sequelizerc
├── index.js
├── app.js
├── package.json
└── yarn.lock
const User = require( "../models" ).User;const checkForDuplicateUsername = ( request, response, next ) => {
User.findOne( { where: { username: request.body.username } } ).then( user => {
if ( user ) {
response.status( 400 ).send( { error: "Username already taken" } );
return;
}
next();
} );
}
module.exports = { checkForDuplicateUsername };
const jwt = require( "jsonwebtoken" );
const config = require( "../config/auth.config.js" );
function verifyToken( request, response, next ) {
let token = request.headers[ "x-access-token" ];
if ( !token ) return response.status( 403 ).send( { error: "No token provided" } );
jwt.verify( token, config.secret, ( error, decoded ) => {
if ( error ) return response.status( 401 ).send( { error: "Unauthorized" } );
request.userId = decoded.id;
next();
} );
}
module.exports = {
verifyToken: verifyToken
};
  • the token to verify
  • the auth key we just configured
  • a callback function with two parameters: any error that pops up, and the decoded token. Any error triggers an immediate 401 response; otherwise, we reach into the decoded token for the userId to log in, stash it in our request, and press onward.
const authorizeJwt = require( "./authorizeJwt" );
const verifySignUp = require( "./verifySignUp" );
module.exports = { authorizeJwt, verifySignUp };

12. Create auth controllers

const database = require( "../models" );
const config = require( "../config/auth.config.js" );
const User = database.User;
const jwt = require( "jsonwebtoken" );
const bcrypt = require( "bcryptjs" );
exports.signUp = ( request, response ) => {
return User.create( {
username: request.body.username,
email: request.body.email,
password: bcrypt.hashSync( request.body.password, 8 )
} )
.then( newUser => response.status( 201 ).send( newUser ) )
.catch( error => response.status( 500 ).send( error ) );
}
exports.signIn = ( request, response ) => {
const signInError = { accessToken: null, error: "Invalid username or password" };
return User.findOne( { where: { username: request.body.username } } )
.then( user => {
if ( !user ) return response.status( 401 ).send( signInError );
const validPassword = bcrypt.compareSync( request.body.password, user.password );
if ( !validPassword ) return response.status( 401 ).send( signInError );
const token = jwt.sign( { id: user.id }, config.secret, { expiresIn: 86400 } );
response.status( 200 ).send( { id: user.id, username: user.username, accessToken: token } );
} )
.catch( error => response.status( 500 ).send( error ) );
}

NEVER EVER commit plaintext passwords to a database!

  • The payload to send (the stuff in the parcel),
  • Our auth key
  • Any config options — some cool stuff you can do here, like setting a token expiration date. This token { expiresIn: 86400 } seconds (24 hours).
const auth = require( "./authController.js" );
const users = require( "./usersController.js" );
const posts = require( "./postsController.js" );
module.exports = {
auth,
posts,
users
};

13. Refactor app/routes and add auth routes

const postsController = require( "../controllers" ).posts;
const usersController = require( "../controllers" ).users;
module.exports = app => {
// Post routes //
app.get( "/posts/:postId", postsController.show );
// User routes //
app.get( "/users/:userId", usersController.show );
};
├── node_modules
├── app
│ └── config
│ └── controllers
│ └── middleware
│ └── migrations
│ └── models
│ └── routes
│ │ └── authRoutes.js
│ │ └── index.js
│ │ └── postRoutes.js
│ │ └── userRoutes.js
│ └── seeders
├── .env
├── .sequelizerc
├── index.js
├── app.js
├── package.json
└── yarn.lock
const { verifySignUp } = require( "../middleware" );
const authController = require( "../controllers" ).auth;
module.exports = app => {
// Headers //
app.use( ( request, response, next ) => {
response.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
} );

// Routes //
app.post( "/signup", [ verifySignUp.checkForDuplicateUsername ], authController.signUp );
app.post( "/signin", authController.signIn );
};
  • We can call middleware methods directly with a new method, app.use(). This middleware function sets a response so it accepts token headers. It’s too short to merit its own separate file in middleware/ so we’ll just stick it here.
  • We can also just pass an array of middleware functions as the second argument in post(). Super easy! It shouldn’t surprise you by now to see Express has methods for HTTP verbs like app.post(), app.patch(), and so on.
A rocker switch is idempotent: pressing “on” when the switch is already on… doesn’t do anything

Why do auth routes respond to POST requests?

// postRoutes.jsconst postsController = require( "../controllers" ).posts;module.exports = app => {
app.get( "/posts/:postId", postsController.show );
};
// userRoutes.jsconst usersController = require( "../controllers" ).users;module.exports = app => {
app.get( "/users/:userId", usersController.show );
};
module.exports = app => {
require( "../routes/authRoutes" )( app );
require( "../routes/postRoutes" )( app );
require( "../routes/userRoutes" )( app );
};

14. Adding auth to other routes

// postsController.jsexports.create = async ( request, response ) => {
return await Post.create( {
title: request.body.title,
content: request.body.content,
userId: request.userId
}, {} )
.then( newPost => Post.findByPk( newPost.id, {} )
.then( newPost => response.status( 201 ).send( newPost ) )
.catch( error => response.status( 400 ).send( error ) )
);
}
  • Our create function is an async function, so we must return await and not just return!
  • We create a post with a title and some content— but also request.userId, which only comes from our middleware!
  • Sequelize also gives us a second options hash after the object we’re creating — I’ve left it empty here but it’s great for serialization.
  • We do some chaining with .then() because we need to make sure things happen in the right order: first create the Post, then send it with an appropriate 201 code and catch any errors along the way.
// postRoutes.jsconst { authorizeJwt } = require( "../middleware" );
const postsController = require( "../controllers" ).posts;
module.exports = app => {
app.get( "/posts/:postId", postsController.show );
app.post( "/posts", [ authorizeJwt.verifyToken ], postsController.create );
};
Here’s what happens when we have.a valid token…
…and when we don’t have a valid token

Next round

--

--

--

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Koa.js and How to create Backend with CRUD Operations

Angular component lifecycle

6 Basic Concepts Every React Developer Needs to Understand

No overload matches this call with styled components TypeScript

Example No overload matches this call.

Headless User Interface Components

Create a Simple Reverse proxy for Node.Js with Apache Web Server.

How to Draw Generative NFT Mushrooms with Three.js 🍄

Using Web Workers in Typescript React

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…

More from Medium

🍪🍪 Cookies with Express.js, Nodemon, ESM, Cookie Parser and Cookie Session

How To Test a NodeJS Module using Mocha and Assert

Using the Youtube API to get live viewer count

How to Talk to a RabbitMQ Instance with Node.js and TypeScript