├── .gitignore ├── README.md ├── app.js ├── config.json ├── create_user.js ├── errors ├── NotFoundError.js └── UnauthorizedAccessError.js ├── keys ├── ca.crt ├── ca.csr ├── ca.key ├── passphrase ├── server.crt ├── server.csr ├── server.key └── server.key.passphrase ├── models └── user.js ├── package.json ├── routes └── default.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Express4 + Mongoose + JSON Web Token Authentication 2 | ==================================================== 3 | 4 | Preparation 5 | ------------ 6 | ``` 7 | npm install 8 | ``` 9 | 10 | Creation of user 11 | ---------------- 12 | ``` 13 | node create_user.js demo demo 14 | ``` 15 | 16 | Starting application 17 | -------------------- 18 | ``` 19 | DEBUG=app:* node app.js 20 | ``` -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require('debug')('app:' + process.pid), 4 | path = require("path"), 5 | fs = require("fs"), 6 | http_port = process.env.HTTP_PORT || 3000, 7 | https_port = process.env.HTTPS_PORT || 3443, 8 | jwt = require("express-jwt"), 9 | config = require("./config.json"), 10 | mongoose_uri = process.env.MONGOOSE_URI || "localhost/express-jwt-auth", 11 | onFinished = require('on-finished'), 12 | NotFoundError = require(path.join(__dirname, "errors", "NotFoundError.js")), 13 | utils = require(path.join(__dirname, "utils.js")), 14 | unless = require('express-unless'); 15 | 16 | debug("Starting application"); 17 | 18 | debug("Loading Mongoose functionality"); 19 | var mongoose = require('mongoose'); 20 | mongoose.set('debug', true); 21 | mongoose.connect(mongoose_uri); 22 | mongoose.connection.on('error', function () { 23 | debug('Mongoose connection error'); 24 | }); 25 | mongoose.connection.once('open', function callback() { 26 | debug("Mongoose connected to the database"); 27 | }); 28 | 29 | debug("Initializing express"); 30 | var express = require('express'), app = express(); 31 | 32 | debug("Attaching plugins"); 33 | app.use(require('morgan')("dev")); 34 | var bodyParser = require("body-parser"); 35 | app.use(bodyParser.json()); 36 | app.use(bodyParser.urlencoded()); 37 | app.use(require('compression')()); 38 | app.use(require('response-time')()); 39 | 40 | app.use(function (req, res, next) { 41 | 42 | onFinished(res, function (err) { 43 | debug("[%s] finished request", req.connection.remoteAddress); 44 | }); 45 | 46 | next(); 47 | 48 | }); 49 | 50 | var jwtCheck = jwt({ 51 | secret: config.secret 52 | }); 53 | jwtCheck.unless = unless; 54 | 55 | app.use(jwtCheck.unless({path: '/api/login' })); 56 | app.use(utils.middleware().unless({path: '/api/login' })); 57 | 58 | app.use("/api", require(path.join(__dirname, "routes", "default.js"))()); 59 | 60 | // all other requests redirect to 404 61 | app.all("*", function (req, res, next) { 62 | next(new NotFoundError("404")); 63 | }); 64 | 65 | // error handler for all the applications 66 | app.use(function (err, req, res, next) { 67 | 68 | var errorType = typeof err, 69 | code = 500, 70 | msg = { message: "Internal Server Error" }; 71 | 72 | switch (err.name) { 73 | case "UnauthorizedError": 74 | code = err.status; 75 | msg = undefined; 76 | break; 77 | case "BadRequestError": 78 | case "UnauthorizedAccessError": 79 | case "NotFoundError": 80 | code = err.status; 81 | msg = err.inner; 82 | break; 83 | default: 84 | break; 85 | } 86 | 87 | return res.status(code).json(msg); 88 | 89 | }); 90 | 91 | debug("Creating HTTP server on port: %s", http_port); 92 | require('http').createServer(app).listen(http_port, function () { 93 | debug("HTTP Server listening on port: %s, in %s mode", http_port, app.get('env')); 94 | }); 95 | 96 | debug("Creating HTTPS server on port: %s", https_port); 97 | require('https').createServer({ 98 | key: fs.readFileSync(path.join(__dirname, "keys", "server.key")), 99 | cert: fs.readFileSync(path.join(__dirname, "keys", "server.crt")), 100 | ca: fs.readFileSync(path.join(__dirname, "keys", "ca.crt")), 101 | requestCert: true, 102 | rejectUnauthorized: false 103 | }, app).listen(https_port, function () { 104 | debug("HTTPS Server listening on port: %s, in %s mode", https_port, app.get('env')); 105 | }); 106 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secret": "Ci23fWtahDYE3dfirAHrJhzrUEoslIxqwcDN9VNhRJCWf8Tyc1F1mqYrjGYF" 3 | } -------------------------------------------------------------------------------- /create_user.js: -------------------------------------------------------------------------------- 1 | var path = require("path"), 2 | config = require("./config.json"), 3 | User = require(path.join(__dirname, "models", "user.js")), 4 | mongoose_uri = process.env.MONGOOSE_URI || "localhost/express-jwt-auth"; 5 | 6 | var args = process.argv.slice(2); 7 | 8 | var username = args[0]; 9 | var password = args[1]; 10 | 11 | if (args.length < 2) { 12 | console.log("usage: node %s %s %s", path.basename(process.argv[1]), "user", "password"); 13 | process.exit(); 14 | } 15 | 16 | console.log("Username: %s", username); 17 | console.log("Password: %s", password); 18 | 19 | console.log("Creating a new user in Mongo"); 20 | 21 | 22 | var mongoose = require('mongoose'); 23 | mongoose.set('debug', true); 24 | mongoose.connect(mongoose_uri); 25 | mongoose.connection.on('error', function () { 26 | console.log('Mongoose connection error', arguments); 27 | }); 28 | mongoose.connection.once('open', function callback() { 29 | console.log("Mongoose connected to the database"); 30 | 31 | var user = new User(); 32 | 33 | user.username = username; 34 | user.password = password; 35 | 36 | user.save(function (err) { 37 | if (err) { 38 | console.log(err); 39 | } else { 40 | console.log(user); 41 | } 42 | process.exit(); 43 | }); 44 | 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /errors/NotFoundError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function NotFoundError(code, error) { 3 | Error.call(this, typeof error === "undefined" ? undefined : error.message); 4 | Error.captureStackTrace(this, this.constructor); 5 | this.name = "NotFoundError"; 6 | this.message = typeof error === "undefined" ? undefined : error.message; 7 | this.code = typeof code === "undefined" ? "404" : code; 8 | this.status = 404; 9 | this.inner = error; 10 | } 11 | 12 | NotFoundError.prototype = Object.create(Error.prototype); 13 | NotFoundError.prototype.constructor = NotFoundError; 14 | 15 | module.exports = NotFoundError; -------------------------------------------------------------------------------- /errors/UnauthorizedAccessError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function UnauthorizedAccessError(code, error) { 3 | Error.call(this, error.message); 4 | Error.captureStackTrace(this, this.constructor); 5 | this.name = "UnauthorizedAccessError"; 6 | this.message = error.message; 7 | this.code = code; 8 | this.status = 401; 9 | this.inner = error; 10 | } 11 | 12 | UnauthorizedAccessError.prototype = Object.create(Error.prototype); 13 | UnauthorizedAccessError.prototype.constructor = UnauthorizedAccessError; 14 | 15 | module.exports = UnauthorizedAccessError; -------------------------------------------------------------------------------- /keys/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICATCCAWoCCQDdRrUtWk6PrzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMB4XDTE0MDkwNTAzMjYwOVoXDTE1MDkwNTAzMjYwOVowRTELMAkG 5 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 6 | IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4Y8/ 7 | Cpg5mzDKmcK5aO2bJ5SrjQDZ1F+AuTKzYxKQcBQJVIzID5whuOVXhRtHYGh+Tgx9 8 | BtbAWQjoqqQfrpe33u0XnuL5EpWQweD5ynYSi5hswq8yqqrkbbXQv2U1Rebu3mDi 9 | 0ji9DXxTDJw1KJf1W1+K07LaZtXWShkobLQhj0UCAwEAATANBgkqhkiG9w0BAQsF 10 | AAOBgQCrqrmFgKNSMAQ7r5dqROYn5UK5/QZfRsmEL4Fwlhh3Vv8k+uyE8ruV+bXX 11 | MTraAWX6ZTM7q7ZZNDfKZeFRAewQ2fXDuViNze2CZTQyMijPvNWXlYSpY/Xsdd8Q 12 | ety8e2VAilhcmOzCwPA1NV4TpeMry6C0X4D4MmLgRUxdyKYVTg== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /keys/ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 3 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB 4 | AQUAA4GNADCBiQKBgQDhjz8KmDmbMMqZwrlo7ZsnlKuNANnUX4C5MrNjEpBwFAlU 5 | jMgPnCG45VeFG0dgaH5ODH0G1sBZCOiqpB+ul7fe7Ree4vkSlZDB4PnKdhKLmGzC 6 | rzKqquRttdC/ZTVF5u7eYOLSOL0NfFMMnDUol/VbX4rTstpm1dZKGShstCGPRQID 7 | AQABoAAwDQYJKoZIhvcNAQELBQADgYEAx8qBgeT8zUr86c6GNfuOS5dLz/uJv4cT 8 | wl2HddzNXbUc4w8hpJyFYwAE/UFsPXngthdVVotFmZPw/ssD2FoPd7ycbDgHWHmz 9 | Jqqr+QUKdl/94Q2yUgdwZ7s9EueLNRIYWGM4OMLAYwZSfdGE8HvWGxAGpQT1+dsf 10 | BKJ/yFrKZIA= 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /keys/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,3EDCD174A92BE587 4 | 5 | 3Za7osYSv+KtbC+n6LRptTdC4u+v2xhpQUvLQaKDx8PfA7d/GrZ5Q2hk/L5z/TXt 6 | 4k/EVMW/i46ZoYy8lmrSj0SeFED8+DXplwg3vhYuvh4Ysi3i9ZKQ5eFmw0XwF0fW 7 | ShlRf8MZ7Bu9kVQTtqz/bq8x+eg6I0Gft+7kRL1mMzh1ccvUsFtHhcRBYhrXXEui 8 | B7CK1SEOl+7IYPx9o1udfeJ98Jfq1QzmQoEsfqbh//dMXaeP2hILjExUSZgmsqAw 9 | kYeFZKBhGagzWWE/Lxr/ezldeQLDRa5V1/tg7FkXFBB8n0V1KsTz21k2MwaFampf 10 | gzolj3gQhmpQQuRX7dtp8li90STTrrcydnCFFpBpeDai/lH6bLUI5OkC6Aj6dG1Z 11 | eyta6O4gr+1QtQD/3VqZJqwh10++IVA8WhMtl25Ra3HCnHxzEvH+4GXzcYOd8nnw 12 | q/0MhlCz+54Uc6q+Wh51el72TQKQdYPwvxILuXBWVXK3HXZqVhjicdkByGx8VI+n 13 | W8+n/m5hNqphZO5hI6RbPI92vj+wAkh3WNQhzDqSJaC4ICP6yjubK1G4GoldT0hh 14 | NFKvMDa88RvZOLVTuapkBodjUs0ddCI/XtmY7Pioq8WzfmpEzsB+IlQefgpInL6D 15 | H+hJrRodWpto17rIfLovn8mQm7ZvJQ83lXR6PyN5RvlgZac+8l3tzVO/NwJ1n/OG 16 | oqcHIM4PulJYyU70QKEbrewnuGz7gtetdPPPA+zhBYC8CSXZ952Rl3wLabKBTnd8 17 | jrtopJ70n3KmhAKKD+yG843RmPJp13H1fiLhQ1XqF3k8WaosZkKn1Q== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /keys/passphrase: -------------------------------------------------------------------------------- 1 | 73JQJEyaZDATzFBGpjC63ZRWuQ3iVN55xd8AU8VR 2 | -------------------------------------------------------------------------------- /keys/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICATCCAWoCCQDxVGPJ41HPQzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMB4XDTE0MDkwNTAzMjExNloXDTE1MDkwNTAzMjExNlowRTELMAkG 5 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 6 | IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtpig 7 | NBezzBO5hNMNkwDm7328OKEMVh2vQdgah0yiaNGshbk4SnJgAXZUVdQRVXX+mQT6 8 | oXm/XW6sjNCjqgVJTWF7aJSzKkA9+Dt1GLCcSkXJBkpEhoEP8crS3a1bAQqJLA+i 9 | 1i1Rjm0BMhHaUcKdBtz91Gi6AZK0edSxj541bqsCAwEAATANBgkqhkiG9w0BAQsF 10 | AAOBgQCPfYTY2E848c11xgCw9nHVCJp41SPO9sFFSi9hdmVxTYZoe/TWYqRQF2qu 11 | SNu/bB312jGfvBpNM3FHctorFdPfQ7moswuz4pXtZld7nsWC6rnmnoNp6ixlj5JI 12 | yF9ZKScojTfbJAF7EZz3h6EzFg03Qg7NpSwga62mFh50h3SYjw== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /keys/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 3 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB 4 | AQUAA4GNADCBiQKBgQC2mKA0F7PME7mE0w2TAObvfbw4oQxWHa9B2BqHTKJo0ayF 5 | uThKcmABdlRV1BFVdf6ZBPqheb9dbqyM0KOqBUlNYXtolLMqQD34O3UYsJxKRckG 6 | SkSGgQ/xytLdrVsBCoksD6LWLVGObQEyEdpRwp0G3P3UaLoBkrR51LGPnjVuqwID 7 | AQABoAAwDQYJKoZIhvcNAQELBQADgYEAAVkzOJoWYZMvCxfPkTY0UN/hrhQumCZG 8 | T4bXw+WR2TNgwjBWsTBZpvT/J7vZuHqoUQFu6/xbTPM2KpIYlzYTQcVicHx6rOsJ 9 | qKM+z5wynFgrfxVUsKlbxWZsKEp1uPtAU93YiVNnPi+q9cSbiXKflvOT7APl+jaa 10 | kQDZBcqkbKg= 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC2mKA0F7PME7mE0w2TAObvfbw4oQxWHa9B2BqHTKJo0ayFuThK 3 | cmABdlRV1BFVdf6ZBPqheb9dbqyM0KOqBUlNYXtolLMqQD34O3UYsJxKRckGSkSG 4 | gQ/xytLdrVsBCoksD6LWLVGObQEyEdpRwp0G3P3UaLoBkrR51LGPnjVuqwIDAQAB 5 | AoGABbhQRHF9o2X2yM9nvHQpx9TTgZ6h6UyywchIRHbFG619zw6XUdW1ZjZTvACR 6 | rnCs0hRS2Z4bvYyLATpD9j6GJkXSLeMUSywe0TR5+E/72n+PpAa/5NSs7HFcGHF3 7 | Y3to5bKTn79nW/OeXsB28RQ1zjZn9Neo2B1S3tOCW4zFr0kCQQDpxK1DSFLIU1Ir 8 | LLM9VOOL7R/2kLhFxsDl1cBp88sT+g+aWmu9BxDR2kTWS7EG46XZ+R0r2/JdSYwd 9 | tVJuTaDdAkEAx/YdDBBn8F+yfx2G/8pFgVrEPITZAlSl8sNHtuRpXOMElQpG5Z2+ 10 | M31rUgph/hObF/I61ufQV1Y13b4iHKxRJwJAKzl4qBY5aF0vtlf/lHmb0YW5AUhl 11 | KlHD3TuW5oBzrm/wbqQFg+BIleT8EXvRceqqAhHz+OYkfGdbBys94LNt6QJBAJY1 12 | tIZOgLXaPY0t0i6udN4CIC3SIe6VRgACYAnstZV4WAXZrX5pq+qkCY2cjRY/Bh/L 13 | 8BVmGGSzN022uYtOirsCQAjZRbIUB/yxgEU991dp2IMlkKFwcQno4Y2VQM/9SufA 14 | npfgCUpQETsLBfFWVvXIfO51ozEu9fn3on5F+lw4DZM= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /keys/server.key.passphrase: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,684D01E11DEFF1F2 4 | 5 | hrk52+tYHW2oRw413OAr8aDckCaKMeBsQfhnt0w3sVV6JYZrXqbgGwxLB9HM5TkQ 6 | do8bUh32088RS/Yk2MpcmYvOS9DElMEH/Xndp//jY1//S94VV3fSdADtF7zueuXG 7 | Kbwq+lzqt3tRfhjOQurvQLUrUfj4qgrfKA6GIq0BqQzJ/1f84dmvxRrQ1K8cT3Zr 8 | TGudgTaSY3KmIFTbJ2ZgOst9g2VbVZOia6kYlwVNaMzJ0m11S1UR2QHyqNR+l1/+ 9 | I1jLh2iGltSokStCaIfBRFsGW1vIIa5+8/pwHXAo1WySRUO0LZ/4OpjK3BC4ETbh 10 | JbMDdBaeqw4jgTa0aywUwzFT1YFIDfT9pJs8h9M39SqSR/jJPvNv/TRp1WaqwMnK 11 | 2/6zdbLSLgokZ/JjxYfsgBcDNmDrADcXZbZSbrHItqs9PuY8VfnraJCh8SjpQcYg 12 | +QnYfGyhZNyEgT7gYmPky5jFfOHkDj2sKkjR/DXZ97z1xtQP2ai+X+G2MTfgxTS+ 13 | /vbx8AHLpsRdnTDsEpDAXwuJLaVTllYCP3C3gP/W6P3LTlH2Vwddr1tKyDl6GCbN 14 | d8mPDtipH6ZQfeGKVT2Pfi0gMZBett+in9jTpe8XOVscItG1MPIe7Mtiju7Rx5hX 15 | 0MPZ8D5JeVt83+SrkV5HGqmAozjVrf8/GK1z6lWHwsP62aC70kIYTegJQzTCZqPa 16 | Ks9sa3AXQOSOohkGgU144Zl26BWjk8jNmrdqC6rdMU3GGGDZrqiLLDtvWmHli0vH 17 | hlsHq82OrnJsVAaf4VDpIK/Oh3R16MQLUHHZHlZ7Y0+gxdnT7KVaSQ== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'), 4 | bcrypt = require("bcryptjs"), 5 | Schema = mongoose.Schema; 6 | 7 | var UserSchema = new Schema({ 8 | 9 | username: { 10 | type: String, 11 | unique: true, 12 | required: true 13 | }, 14 | 15 | password: { 16 | type: String, 17 | required: true 18 | } 19 | 20 | }, { 21 | toObject: { 22 | virtuals: true 23 | }, toJSON: { 24 | virtuals: true 25 | } 26 | }); 27 | 28 | UserSchema.pre('save', function (next) { 29 | var user = this; 30 | if (this.isModified('password') || this.isNew) { 31 | bcrypt.genSalt(10, function (err, salt) { 32 | if (err) { 33 | return next(err); 34 | } 35 | bcrypt.hash(user.password, salt, function (err, hash) { 36 | if (err) { 37 | return next(err); 38 | } 39 | user.password = hash; 40 | next(); 41 | }); 42 | }); 43 | } else { 44 | return next(); 45 | } 46 | }); 47 | 48 | UserSchema.methods.comparePassword = function (passw, cb) { 49 | bcrypt.compare(passw, this.password, function (err, isMatch) { 50 | if (err) { 51 | return cb(err); 52 | } 53 | cb(null, isMatch); 54 | }); 55 | }; 56 | 57 | module.exports = mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-jwt-auth", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "app.js", 6 | "dependencies": { 7 | "bcryptjs": "~2.0.2", 8 | "body-parser": "~1.0.0", 9 | "compression": "^1.0.11", 10 | "cors": "^2.4.1", 11 | "debug": "~0.7.4", 12 | "express": "^4.2.0", 13 | "express-jwt": "^0.3.1", 14 | "express-unless": "*", 15 | "hiredis": "^0.1.17", 16 | "jsonwebtoken": "^0.4.1", 17 | "lodash": "~2.4.1", 18 | "mongoose": "~3.8.14", 19 | "morgan": "~1.2.2", 20 | "on-finished": "^2.1.0", 21 | "redis": "^0.12.1", 22 | "response-time": "~2.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /routes/default.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require('debug')('app:routes:default' + process.pid), 4 | _ = require("lodash"), 5 | util = require('util'), 6 | path = require('path'), 7 | bcrypt = require('bcryptjs'), 8 | utils = require("../utils.js"), 9 | Router = require("express").Router, 10 | UnauthorizedAccessError = require(path.join(__dirname, "..", "errors", "UnauthorizedAccessError.js")), 11 | User = require(path.join(__dirname, "..", "models", "user.js")), 12 | jwt = require("express-jwt"); 13 | 14 | var authenticate = function (req, res, next) { 15 | 16 | debug("Processing authenticate middleware"); 17 | 18 | var username = req.body.username, 19 | password = req.body.password; 20 | 21 | if (_.isEmpty(username) || _.isEmpty(password)) { 22 | return next(new UnauthorizedAccessError("401", { 23 | message: 'Invalid username or password' 24 | })); 25 | } 26 | 27 | process.nextTick(function () { 28 | 29 | User.findOne({ 30 | username: username 31 | }, function (err, user) { 32 | 33 | if (err || !user) { 34 | return next(new UnauthorizedAccessError("401", { 35 | message: 'Invalid username or password' 36 | })); 37 | } 38 | 39 | user.comparePassword(password, function (err, isMatch) { 40 | if (isMatch && !err) { 41 | debug("User authenticated, generating token"); 42 | utils.create(user, req, res, next); 43 | } else { 44 | return next(new UnauthorizedAccessError("401", { 45 | message: 'Invalid username or password' 46 | })); 47 | } 48 | }); 49 | }); 50 | 51 | }); 52 | 53 | 54 | }; 55 | 56 | module.exports = function () { 57 | 58 | var router = new Router(); 59 | 60 | router.route("/verify").get(function (req, res, next) { 61 | return res.status(200).json(undefined); 62 | }); 63 | 64 | router.route("/logout").get(function (req, res, next) { 65 | if (utils.expire(req.headers)) { 66 | delete req.user; 67 | return res.status(200).json({ 68 | "message": "User has been successfully logged out" 69 | }); 70 | } else { 71 | return next(new UnauthorizedAccessError("401")); 72 | } 73 | }); 74 | 75 | router.route("/login").post(authenticate, function (req, res, next) { 76 | return res.status(200).json(req.user); 77 | }); 78 | 79 | router.unless = require("express-unless"); 80 | 81 | return router; 82 | }; 83 | 84 | debug("Loaded"); 85 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require('debug')('app:utils:' + process.pid), 4 | path = require('path'), 5 | util = require('util'), 6 | redis = require("redis"), 7 | client = redis.createClient(), 8 | _ = require("lodash"), 9 | config = require("./config.json"), 10 | jsonwebtoken = require("jsonwebtoken"), 11 | TOKEN_EXPIRATION = 60, 12 | TOKEN_EXPIRATION_SEC = TOKEN_EXPIRATION * 60, 13 | UnauthorizedAccessError = require(path.join(__dirname, 'errors', 'UnauthorizedAccessError.js')); 14 | 15 | client.on('error', function (err) { 16 | debug(err); 17 | }); 18 | 19 | client.on('connect', function () { 20 | debug("Redis successfully connected"); 21 | }); 22 | 23 | /** 24 | * Find the authorization headers from the headers in the request 25 | * 26 | * @param headers 27 | * @returns {*} 28 | */ 29 | module.exports.fetch = function (headers) { 30 | if (headers && headers.authorization) { 31 | var authorization = headers.authorization; 32 | var part = authorization.split(' '); 33 | if (part.length === 2) { 34 | var token = part[1]; 35 | return part[1]; 36 | } else { 37 | return null; 38 | } 39 | } else { 40 | return null; 41 | } 42 | }; 43 | 44 | /** 45 | * Creates a new token for the user that has been logged in 46 | * 47 | * @param user 48 | * @param req 49 | * @param res 50 | * @param next 51 | * 52 | * @returns {*} 53 | */ 54 | module.exports.create = function (user, req, res, next) { 55 | 56 | debug("Create token"); 57 | 58 | if (_.isEmpty(user)) { 59 | return next(new Error('User data cannot be empty.')); 60 | } 61 | 62 | var data = { 63 | _id: user._id, 64 | username: user.username, 65 | access: user.access, 66 | name: user.name, 67 | email: user.email, 68 | token: jsonwebtoken.sign({ _id: user._id }, config.secret, { 69 | expiresInMinutes: TOKEN_EXPIRATION 70 | }) 71 | }; 72 | 73 | var decoded = jsonwebtoken.decode(data.token); 74 | 75 | data.token_exp = decoded.exp; 76 | data.token_iat = decoded.iat; 77 | 78 | debug("Token generated for user: %s, token: %s", data.username, data.token); 79 | 80 | client.set(data.token, JSON.stringify(data), function (err, reply) { 81 | if (err) { 82 | return next(new Error(err)); 83 | } 84 | 85 | if (reply) { 86 | client.expire(data.token, TOKEN_EXPIRATION_SEC, function (err, reply) { 87 | if (err) { 88 | return next(new Error("Can not set the expire value for the token key")); 89 | } 90 | if (reply) { 91 | req.user = data; 92 | next(); // we have succeeded 93 | } else { 94 | return next(new Error('Expiration not set on redis')); 95 | } 96 | }); 97 | } 98 | else { 99 | return next(new Error('Token not set in redis')); 100 | } 101 | }); 102 | 103 | return data; 104 | 105 | }; 106 | 107 | /** 108 | * Fetch the token from redis for the given key 109 | * 110 | * @param id 111 | * @param done 112 | * @returns {*} 113 | */ 114 | module.exports.retrieve = function (id, done) { 115 | 116 | debug("Calling retrieve for token: %s", id); 117 | 118 | if (_.isNull(id)) { 119 | return done(new Error("token_invalid"), { 120 | "message": "Invalid token" 121 | }); 122 | } 123 | 124 | client.get(id, function (err, reply) { 125 | if (err) { 126 | return done(err, { 127 | "message": err 128 | }); 129 | } 130 | 131 | if (_.isNull(reply)) { 132 | return done(new Error("token_invalid"), { 133 | "message": "Token doesn't exists, are you sure it hasn't expired or been revoked?" 134 | }); 135 | } else { 136 | var data = JSON.parse(reply); 137 | debug("User data fetched from redis store for user: %s", data.username); 138 | 139 | if (_.isEqual(data.token, id)) { 140 | return done(null, data); 141 | } else { 142 | return done(new Error("token_doesnt_exist"), { 143 | "message": "Token doesn't exists, login into the system so it can generate new token." 144 | }); 145 | } 146 | 147 | } 148 | 149 | }); 150 | 151 | }; 152 | 153 | /** 154 | * Verifies that the token supplied in the request is valid, by checking the redis store to see if it's stored there. 155 | * 156 | * @param req 157 | * @param res 158 | * @param next 159 | */ 160 | module.exports.verify = function (req, res, next) { 161 | 162 | debug("Verifying token"); 163 | 164 | var token = exports.fetch(req.headers); 165 | 166 | jsonwebtoken.verify(token, config.secret, function (err, decode) { 167 | 168 | if (err) { 169 | req.user = undefined; 170 | return next(new UnauthorizedAccessError("invalid_token")); 171 | } 172 | 173 | exports.retrieve(token, function (err, data) { 174 | 175 | if (err) { 176 | req.user = undefined; 177 | return next(new UnauthorizedAccessError("invalid_token", data)); 178 | } 179 | 180 | req.user = data; 181 | next(); 182 | 183 | }); 184 | 185 | }); 186 | }; 187 | 188 | /** 189 | * Expires the token, so the user can no longer gain access to the system, without logging in again or requesting new token 190 | * 191 | * @param headers 192 | * @returns {boolean} 193 | */ 194 | module.exports.expire = function (headers) { 195 | 196 | var token = exports.fetch(headers); 197 | 198 | debug("Expiring token: %s", token); 199 | 200 | if (token !== null) { 201 | client.expire(token, 0); 202 | } 203 | 204 | return token !== null; 205 | 206 | }; 207 | 208 | /** 209 | * Middleware for getting the token into the user 210 | * 211 | * @param req 212 | * @param res 213 | * @param next 214 | */ 215 | module.exports.middleware = function () { 216 | 217 | var func = function (req, res, next) { 218 | 219 | var token = exports.fetch(req.headers); 220 | 221 | exports.retrieve(token, function (err, data) { 222 | 223 | if (err) { 224 | req.user = undefined; 225 | return next(new UnauthorizedAccessError("invalid_token", data)); 226 | } else { 227 | req.user = _.merge(req.user, data); 228 | next(); 229 | } 230 | 231 | }); 232 | }; 233 | 234 | func.unless = require("express-unless"); 235 | 236 | return func; 237 | 238 | }; 239 | 240 | module.exports.TOKEN_EXPIRATION = TOKEN_EXPIRATION; 241 | module.exports.TOKEN_EXPIRATION_SEC = TOKEN_EXPIRATION_SEC; 242 | 243 | debug("Loaded"); --------------------------------------------------------------------------------