├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── config.json ├── db.js ├── jsconfig.json ├── package.json ├── public └── stylesheets │ └── style.css ├── roles.js ├── routes ├── index.js ├── quotes.js ├── todos.js └── users.js └── views ├── error.jade ├── index.jade └── layout.jade /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | typings/ 3 | .vscode/ 4 | *.log 5 | *.log.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the source code for my article: 2 | [Build JWT authentication server with Node.js, Express and MySQL](https://vivavivugeek.blogspot.com/2016/09/build-jwt-authentication-server-with.html) 3 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var cors = require('cors'); //add cors 8 | 9 | var routes = require('./routes/index'); 10 | var users = require('./routes/users'); 11 | var quotes = require('./routes/quotes'); //quotes 12 | var todos = require('./routes/todos'); //quotes 13 | 14 | var app = express(); 15 | 16 | // view engine setup 17 | app.set('views', path.join(__dirname, 'views')); 18 | app.set('view engine', 'jade'); 19 | 20 | // uncomment after placing your favicon in /public 21 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 22 | app.use(logger('dev')); 23 | app.use(cors()); //add cors 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({ extended: true })); //false ==> true 26 | app.use(cookieParser()); 27 | app.use(express.static(path.join(__dirname, 'public'))); 28 | 29 | app.use('/', routes); 30 | //app.use('/users', users); //change to below line 31 | app.use(users); 32 | app.use(quotes); //quotes 33 | app.use(todos); //todos 34 | 35 | // catch 404 and forward to error handler 36 | app.use(function(req, res, next) { 37 | var err = new Error('Not Found'); 38 | err.status = 404; 39 | next(err); 40 | }); 41 | 42 | // error handlers 43 | 44 | // development error handler 45 | // will print stacktrace 46 | if (app.get('env') === 'development') { 47 | app.use(function(err, req, res, next) { 48 | res.status(err.status || 500); 49 | res.render('error', { 50 | message: err.message, 51 | error: err 52 | }); 53 | }); 54 | } 55 | 56 | // production error handler 57 | // no stacktraces leaked to user 58 | app.use(function(err, req, res, next) { 59 | res.status(err.status || 500); 60 | res.render('error', { 61 | message: err.message, 62 | error: {} 63 | }); 64 | }); 65 | 66 | 67 | module.exports = app; 68 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('mysql-jwt-auth:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * DB Connection 13 | */ 14 | var db = require('../db'); 15 | db.connect(); 16 | 17 | /** 18 | * Get port from environment and store in Express. 19 | */ 20 | 21 | var port = normalizePort(process.env.PORT || '3000'); 22 | app.set('port', port); 23 | 24 | /** 25 | * Create HTTP server. 26 | */ 27 | 28 | var server = http.createServer(app); 29 | 30 | /** 31 | * Listen on provided port, on all network interfaces. 32 | */ 33 | 34 | server.listen(port); 35 | server.on('error', onError); 36 | server.on('listening', onListening); 37 | 38 | /** 39 | * Normalize a port into a number, string, or false. 40 | */ 41 | 42 | function normalizePort(val) { 43 | var port = parseInt(val, 10); 44 | 45 | if (isNaN(port)) { 46 | // named pipe 47 | return val; 48 | } 49 | 50 | if (port >= 0) { 51 | // port number 52 | return port; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /** 59 | * Event listener for HTTP server "error" event. 60 | */ 61 | 62 | function onError(error) { 63 | if (error.syscall !== 'listen') { 64 | throw error; 65 | } 66 | 67 | var bind = typeof port === 'string' 68 | ? 'Pipe ' + port 69 | : 'Port ' + port; 70 | 71 | // handle specific listen errors with friendly messages 72 | switch (error.code) { 73 | case 'EACCES': 74 | console.error(bind + ' requires elevated privileges'); 75 | process.exit(1); 76 | break; 77 | case 'EADDRINUSE': 78 | console.error(bind + ' is already in use'); 79 | process.exit(1); 80 | break; 81 | default: 82 | throw error; 83 | } 84 | } 85 | 86 | /** 87 | * Event listener for HTTP server "listening" event. 88 | */ 89 | 90 | function onListening() { 91 | var addr = server.address(); 92 | var bind = typeof addr === 'string' 93 | ? 'pipe ' + addr 94 | : 'port ' + addr.port; 95 | debug('Listening on ' + bind); 96 | } 97 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secretKey": "don't share this key" 3 | } -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var pool = null; 3 | 4 | exports.connect = function() { 5 | pool = mysql.createPool({ 6 | host : 'myhost', 7 | user : 'myuser', 8 | password : 'mypassword', 9 | database : 'mydb' 10 | }); 11 | } 12 | 13 | exports.get = function() { 14 | return pool; 15 | } 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql-jwt-auth", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.15.1", 10 | "cookie-parser": "~1.4.3", 11 | "cors": "^2.8.1", 12 | "debug": "~2.2.0", 13 | "express": "~4.13.4", 14 | "express-jwt": "^5.0.0", 15 | "jade": "~1.11.0", 16 | "jsonwebtoken": "^7.1.9", 17 | "lodash": "^4.15.0", 18 | "morgan": "~1.7.0", 19 | "mysql": "^2.11.1", 20 | "serve-favicon": "~2.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /roles.js: -------------------------------------------------------------------------------- 1 | var db = require('./db'); 2 | 3 | exports.checkRole = function(roles){ 4 | return function(req, res, next){ 5 | var user = req.user; 6 | db.get().query('SELECT * FROM users WHERE username = ? LIMIT 1', [user.username], function(err, rows) { 7 | if(err){ 8 | res.status(422).json({error: 'No user found.'}); 9 | return next(err); 10 | } 11 | var foundUser = rows[0]; 12 | if(roles.indexOf(foundUser.role) > -1){ 13 | return next(); 14 | } 15 | db.get().query('SELECT * FROM todos WHERE id=? AND username=?', [req.params.id, user.username], function(err, rows) { 16 | if(err){ 17 | res.status(401).json({error: 'You are not authorized to do this action.'}); 18 | return next('Unauthorized'); 19 | } 20 | return next(); 21 | }); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express of Hung' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/quotes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | jwt = require('express-jwt'), 3 | config = require('../config'), 4 | db = require('../db'); 5 | 6 | var app = module.exports = express.Router(); 7 | 8 | var jwtCheck = jwt({ 9 | secret: config.secretKey 10 | }); 11 | 12 | function getPublicQuotesDB(done){ 13 | db.get().query('SELECT * FROM quotes WHERE private=0', function(err, rows) { 14 | if (err) throw err; 15 | done(rows); 16 | }); 17 | } 18 | 19 | function getPrivateQuotesDB(done){ 20 | db.get().query('SELECT * FROM quotes WHERE private=1', function(err, rows) { 21 | if (err) throw err; 22 | done(rows); 23 | }); 24 | } 25 | 26 | app.get('/api/public/quote', function(req, res) { 27 | getPublicQuotesDB(function(result) { 28 | res.status(200).send(result); 29 | }); 30 | }); 31 | 32 | app.use('/api/private', jwtCheck); 33 | 34 | app.get('/api/private/quote', function(req, res) { 35 | getPrivateQuotesDB(function(result) { 36 | res.status(200).send(result); 37 | }); 38 | }); -------------------------------------------------------------------------------- /routes/todos.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | roles = require('../roles'); 3 | jwt = require('express-jwt'), 4 | config = require('../config'), 5 | db = require('../db'); 6 | 7 | var app = module.exports = express.Router(); 8 | 9 | var jwtCheck = jwt({ 10 | secret: config.secretKey 11 | }); 12 | 13 | function getTodosDB(username, done) { 14 | db.get().query('SELECT * FROM todos WHERE username = ? OR public = 1', [username], function(err, rows) { 15 | if (err) throw err; 16 | done(rows); 17 | }); 18 | } 19 | 20 | app.get('/todos', jwtCheck, function(req, res) { 21 | getTodosDB(req.user.username, function(rows){ 22 | res.status(201).send(rows); 23 | }); 24 | }); 25 | 26 | app.post('/todos', jwtCheck, function(req, res) { 27 | todo = { 28 | task: req.body.task, 29 | deadline: req.body.deadline, 30 | public: req.body.public, 31 | completed: 0, 32 | username: req.user.username 33 | }; 34 | db.get().query('INSERT INTO todos SET ?', [todo], function(err, result){ 35 | if (err) throw err; 36 | newTodo = { 37 | id: result.insertId, 38 | task: todo.task, 39 | deadline: todo.deadline, 40 | public: todo.public, 41 | completed: todo.completed, 42 | username: todo.username 43 | }; 44 | res.status(201).send(newTodo); 45 | }); 46 | }); 47 | 48 | app.delete('/todos/:id', jwtCheck, roles.checkRole(['Moderator','Administrator']), function(req, res){ 49 | db.get().query('DELETE FROM todos WHERE id = ?', [req.params.id], function(err, result){ 50 | if (err) throw err; 51 | res.status(201).send({result: "OK"}); 52 | }); 53 | }); -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | _ = require('lodash'), 3 | config = require('../config'), 4 | jwt = require('jsonwebtoken'), 5 | ejwt = require('express-jwt'), 6 | db = require('../db'); 7 | 8 | var app = module.exports = express.Router(); 9 | 10 | var jwtCheck = ejwt({ 11 | secret: config.secretKey 12 | }); 13 | 14 | function createToken(user) { 15 | return jwt.sign(_.omit(user, 'password'), config.secretKey, { expiresIn: 60*60*5 }); 16 | } 17 | 18 | function getUserDB(username, done) { 19 | db.get().query('SELECT * FROM users WHERE username = ? LIMIT 1', [username], function(err, rows, fields) { 20 | if (err) throw err; 21 | //console.log(rows[0]); 22 | done(rows[0]); 23 | }); 24 | } 25 | 26 | app.post('/user/create', function(req, res) { 27 | if (!req.body.username || !req.body.password) { 28 | return res.status(400).send("You must send the username and the password"); 29 | } 30 | 31 | getUserDB(req.body.username, function(user){ 32 | if(!user) { 33 | user = { 34 | username: req.body.username, 35 | password: req.body.password, 36 | email: req.body.email, 37 | role: 'Regitered' 38 | }; 39 | db.get().query('INSERT INTO users SET ?', [user], function(err, result){ 40 | if (err) throw err; 41 | newUser = { 42 | id: result.insertId, 43 | username: user.username, 44 | password: user.password, 45 | email: user.email, 46 | role: 'Regitered' 47 | }; 48 | //console.log(newUser); 49 | res.status(201).send({ 50 | id_token: createToken(newUser) 51 | }); 52 | }); 53 | } 54 | else res.status(400).send("A user with that username already exists"); 55 | }); 56 | }); 57 | 58 | app.post('/user/login', function(req, res) { 59 | if (!req.body.username || !req.body.password) { 60 | return res.status(400).send("You must send the username and the password"); 61 | } 62 | 63 | getUserDB(req.body.username, function(user){ 64 | if (!user) { 65 | return res.status(401).send("The username is not existing"); 66 | } 67 | 68 | if (user.password !== req.body.password) { 69 | return res.status(401).send("The username or password don't match"); 70 | } 71 | 72 | res.status(201).send({ 73 | id_token: createToken(user) 74 | }); 75 | }); 76 | }); 77 | 78 | app.get('/user/check/:username', function(req, res) { 79 | if (!req.params.username) { 80 | return res.status(400).send("You must send a username"); 81 | } 82 | 83 | getUserDB(req.params.username, function(user){ 84 | if(!user) res.status(201).send({username: "OK"}); 85 | else res.status(400).send("A user with that username already exists"); 86 | }); 87 | }); 88 | 89 | app.get('/user/verify', jwtCheck, function(req, res){ 90 | var user = req.user; 91 | db.get().query('SELECT * FROM users WHERE username = ? LIMIT 1', [user.username], function(err, rows) { 92 | if(err){ 93 | res.status(422).send({error: 'No user found.'}); 94 | } 95 | var foundUser = rows[0]; 96 | res.send(foundUser); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | --------------------------------------------------------------------------------