Skip to content

Commit

Permalink
feat(JWT): Add support for JWT Authentication (#30)
Browse files Browse the repository at this point in the history
Closes #27
  • Loading branch information
kunalkapadia authored Aug 20, 2016
1 parent c035504 commit 4917e6b
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## Overview

This is a boilerplate application for building REST APIs in Node.js using ES6 and Express with Code Coverage. Helps you stay productive by following best practices. Follows [Airbnb's Javascript style guide](https://github.com/airbnb/javascript).
This is a boilerplate application for building REST APIs in Node.js using ES6 and Express with Code Coverage and JWT Authentication. Helps you stay productive by following best practices. Follows [Airbnb's Javascript style guide](https://github.com/airbnb/javascript).

Heavily inspired from [Egghead.io - How to Write an Open Source JavaScript Library](https://egghead.io/courses/how-to-write-an-open-source-javascript-library).

Expand All @@ -22,6 +22,7 @@ Heavily inspired from [Egghead.io - How to Write an Open Source JavaScript Libra
| Feature | Summary |
|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ES6 via Babel | ES6 support using [Babel](https://babeljs.io/). |
| Authentication via JsonWebToken | Supports authentication using [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). |
| Code Linting | JavaScript code linting is done using [ESLint](http://eslint.org) - a pluggable linter tool for identifying and reporting on patterns in JavaScript. Uses ESLint with [eslint-config-airbnb](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb), which tries to follow the Airbnb JavaScript style guide. |
| Auto server restart | Restart the server using [nodemon](https://github.com/remy/nodemon) in real-time anytime an edit is made, with babel compilation and eslint. |
| ES6 Code Coverage via [istanbul](https://www.npmjs.com/package/istanbul) | Supports code coverage of ES6 code using istanbul and mocha. Code coverage reports are saved in `coverage/` directory post `npm test` execution. Open `lcov-report/index.html` to view coverage report. `npm test` also displays code coverage summary on console. |
Expand Down
1 change: 1 addition & 0 deletions config/env/development.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default {
env: 'development',
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
db: 'mongodb://localhost/express-mongoose-es6-rest-api-development',
port: 3000
};
1 change: 1 addition & 0 deletions config/env/production.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default {
env: 'production',
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
db: 'mongodb://localhost/express-mongoose-es6-rest-api-production',
port: 3000
};
1 change: 1 addition & 0 deletions config/env/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default {
env: 'test',
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
db: 'mongodb://localhost/express-mongoose-es6-rest-api-test',
port: 3000
};
8 changes: 8 additions & 0 deletions config/param-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ export default {
params: {
userId: Joi.string().hex().required()
}
},

// POST /api/auth/login
login: {
body: {
username: Joi.string().required(),
password: Joi.string().required()
}
}
};
3 changes: 1 addition & 2 deletions gulpfile.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ const options = {
codeCoverage: {
reporters: ['lcov', 'text-summary'],
thresholds: {
global: { statements: 80, branches: 80, functions: 80, lines: 80 },
each: { statements: 50, branches: 50, functions: 50, lines: 50 }
global: { statements: 80, branches: 80, functions: 80, lines: 80 }
}
}
};
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@
"cors": "^2.7.1",
"debug": "^2.2.0",
"express": "4.14.0",
"express-jwt": "3.4.0",
"express-validation": "1.0.0",
"express-winston": "^1.2.0",
"helmet": "2.1.1",
"http-status": "^0.2.0",
"joi": "8.4.2",
"jsonwebtoken": "7.1.9",
"method-override": "^2.3.5",
"mongoose": "^4.3.7",
"morgan": "1.7.0",
Expand Down
51 changes: 51 additions & 0 deletions server/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import jwt from 'jsonwebtoken';
import httpStatus from 'http-status';
import APIError from '../helpers/APIError';

const config = require('../../config/env');

// sample user, used for authentication
const user = {
username: 'react',
password: 'express'
};

/**
* Returns jwt token if valid username and password is provided
* @param req
* @param res
* @param next
* @returns {*}
*/
function login(req, res, next) {
// Ideally you'll fetch this from the db
// Idea here was to show how jwt works with simplicity
if (req.body.username === user.username && req.body.password === user.password) {
const token = jwt.sign({
username: user.username
}, config.jwtSecret);
return res.json({
token,
username: user.username
});
}

const err = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
return next(err);
}

/**
* This is a protected route. Will return random number only if jwt token is provided in header.
* @param req
* @param res
* @returns {*}
*/
function getRandomNumber(req, res) {
// req.user is assigned by jwt middleware if valid token is provided
return res.json({
user: req.user,
num: Math.random() * 100
});
}

export default { login, getRandomNumber };
19 changes: 19 additions & 0 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import express from 'express';
import validate from 'express-validation';
import expressJwt from 'express-jwt';
import paramValidation from '../../config/param-validation';
import authCtrl from '../controllers/auth';
import config from '../../config/env';

const router = express.Router(); // eslint-disable-line new-cap

/** POST /api/auth/login - Returns token if correct username and password is provided */
router.route('/login')
.post(validate(paramValidation.login), authCtrl.login);

/** GET /api/auth/random-number - Protected route,
* needs token returned by the above as header. Authorization: Bearer {token} */
router.route('/random-number')
.get(expressJwt({ secret: config.jwtSecret }), authCtrl.getRandomNumber);

export default router;
4 changes: 4 additions & 0 deletions server/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from 'express';
import userRoutes from './user';
import authRoutes from './auth';

const router = express.Router(); // eslint-disable-line new-cap

Expand All @@ -11,4 +12,7 @@ router.get('/health-check', (req, res) =>
// mount user routes at /users
router.use('/users', userRoutes);

// mount auth routes at /auth
router.use('/auth', authRoutes);

export default router;

0 comments on commit 4917e6b

Please sign in to comment.