Skip to content

Commit 4917e6b

Browse files
authored
feat(JWT): Add support for JWT Authentication (#30)
Closes #27
1 parent c035504 commit 4917e6b

File tree

10 files changed

+90
-3
lines changed

10 files changed

+90
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
## Overview
1515

16-
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).
16+
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).
1717

1818
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).
1919

@@ -22,6 +22,7 @@ Heavily inspired from [Egghead.io - How to Write an Open Source JavaScript Libra
2222
| Feature | Summary |
2323
|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
2424
| ES6 via Babel | ES6 support using [Babel](https://babeljs.io/). |
25+
| Authentication via JsonWebToken | Supports authentication using [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). |
2526
| 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. |
2627
| 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. |
2728
| 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. |

config/env/development.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
env: 'development',
3+
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
34
db: 'mongodb://localhost/express-mongoose-es6-rest-api-development',
45
port: 3000
56
};

config/env/production.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
env: 'production',
3+
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
34
db: 'mongodb://localhost/express-mongoose-es6-rest-api-production',
45
port: 3000
56
};

config/env/test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export default {
22
env: 'test',
3+
jwtSecret: '0a6b944d-d2fb-46fc-a85e-0295c986cd9f',
34
db: 'mongodb://localhost/express-mongoose-es6-rest-api-test',
45
port: 3000
56
};

config/param-validation.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,13 @@ export default {
1818
params: {
1919
userId: Joi.string().hex().required()
2020
}
21+
},
22+
23+
// POST /api/auth/login
24+
login: {
25+
body: {
26+
username: Joi.string().required(),
27+
password: Joi.string().required()
28+
}
2129
}
2230
};

gulpfile.babel.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ const options = {
1818
codeCoverage: {
1919
reporters: ['lcov', 'text-summary'],
2020
thresholds: {
21-
global: { statements: 80, branches: 80, functions: 80, lines: 80 },
22-
each: { statements: 50, branches: 50, functions: 50, lines: 50 }
21+
global: { statements: 80, branches: 80, functions: 80, lines: 80 }
2322
}
2423
}
2524
};

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
"cors": "^2.7.1",
4343
"debug": "^2.2.0",
4444
"express": "4.14.0",
45+
"express-jwt": "3.4.0",
4546
"express-validation": "1.0.0",
4647
"express-winston": "^1.2.0",
4748
"helmet": "2.1.1",
4849
"http-status": "^0.2.0",
4950
"joi": "8.4.2",
51+
"jsonwebtoken": "7.1.9",
5052
"method-override": "^2.3.5",
5153
"mongoose": "^4.3.7",
5254
"morgan": "1.7.0",

server/controllers/auth.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import jwt from 'jsonwebtoken';
2+
import httpStatus from 'http-status';
3+
import APIError from '../helpers/APIError';
4+
5+
const config = require('../../config/env');
6+
7+
// sample user, used for authentication
8+
const user = {
9+
username: 'react',
10+
password: 'express'
11+
};
12+
13+
/**
14+
* Returns jwt token if valid username and password is provided
15+
* @param req
16+
* @param res
17+
* @param next
18+
* @returns {*}
19+
*/
20+
function login(req, res, next) {
21+
// Ideally you'll fetch this from the db
22+
// Idea here was to show how jwt works with simplicity
23+
if (req.body.username === user.username && req.body.password === user.password) {
24+
const token = jwt.sign({
25+
username: user.username
26+
}, config.jwtSecret);
27+
return res.json({
28+
token,
29+
username: user.username
30+
});
31+
}
32+
33+
const err = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
34+
return next(err);
35+
}
36+
37+
/**
38+
* This is a protected route. Will return random number only if jwt token is provided in header.
39+
* @param req
40+
* @param res
41+
* @returns {*}
42+
*/
43+
function getRandomNumber(req, res) {
44+
// req.user is assigned by jwt middleware if valid token is provided
45+
return res.json({
46+
user: req.user,
47+
num: Math.random() * 100
48+
});
49+
}
50+
51+
export default { login, getRandomNumber };

server/routes/auth.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import express from 'express';
2+
import validate from 'express-validation';
3+
import expressJwt from 'express-jwt';
4+
import paramValidation from '../../config/param-validation';
5+
import authCtrl from '../controllers/auth';
6+
import config from '../../config/env';
7+
8+
const router = express.Router(); // eslint-disable-line new-cap
9+
10+
/** POST /api/auth/login - Returns token if correct username and password is provided */
11+
router.route('/login')
12+
.post(validate(paramValidation.login), authCtrl.login);
13+
14+
/** GET /api/auth/random-number - Protected route,
15+
* needs token returned by the above as header. Authorization: Bearer {token} */
16+
router.route('/random-number')
17+
.get(expressJwt({ secret: config.jwtSecret }), authCtrl.getRandomNumber);
18+
19+
export default router;

server/routes/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import express from 'express';
22
import userRoutes from './user';
3+
import authRoutes from './auth';
34

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

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

15+
// mount auth routes at /auth
16+
router.use('/auth', authRoutes);
17+
1418
export default router;

0 commit comments

Comments
 (0)