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

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
};
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!

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 );
};
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 ) )
);
}
// 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

--

--

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

55 Followers

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