Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(JWT): Add support for JWT Authentication (#29) #30

Merged
merged 1 commit into from
Aug 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;