Reputation: 133
I am trying to build a simple web token protected api in nodejs. I have been following this tutorial authenticate a node js api with json web tokens and have been implementing the steps in my app. I now have an api running that allows me to get/post/put/delete and a route that generates a webtoken for the user and shows it in plain text (for dev purposes). I am using node-restful for the api's but I am having some trouble understanding how I would actually verify if the client is sending the webtoken in their request, before allowing these get/post/put/delete requests.
Here is my router. Where I define the allowed requests:
const express = require('express')
const router = express.Router()
// Models - Load models here
var userModel = require('./models/User')
// Controllers - Load controllers here
const userController = require('./controllers/userController')
// Routes - Define routes here
router.post('api/authenticate', userController.authenticate) //Route that generates the webkey and shows it in the response
// Configure the endpoint that node-restful will expose. Here I want to first check if the user is sending his or her api key. Before allowing these methods.
userModel.methods(['get', 'put', 'post', 'delete'])
userModel.register(router, '/api/users')
// Export the router object
module.exports = router
Here is my userController where the token is generated.
// Dependencies
const User = require('../models/User')
const jwt = require('jsonwebtoken')
const config = require('../config.js')
module.exports = {
authenticate: function(req, res, next) {
// find the user
User.findOne({username: req.body.name}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({
success: false,
message: 'Authentication failed. User not found.' });
} else if (user) {
// check if password matches
if (user.password != req.body.password) {
res.json({
success: false,
message: 'Authentication failed. Wrong password.' });
} else {
// if user is found and password is right
// create a token
var token = jwt.sign(user, config.secret, {
expiresIn: 60*60*24 // expires in 24 hours
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
})
}
}
And here is my user model.
// Dependencies
const restful = require('node-restful')
const mongoose = restful.mongoose
// Schema
const userSchema = new mongoose.Schema({
username: String,
password: String,
email: String
})
// Return the model as a restful model to allow it being used as a route.
module.exports = restful.model('User', userSchema)
Is there some way I can protect these endpoints, using the same manner of syntax as I am currently using to expose them? I believe I would have to check for the web token before defining the methods:
userModel.methods(['get', 'put', 'post', 'delete'])
userModel.register(router, '/api/users')
If I simply remove the methods themselves, the user will not be able to get the page and is shown a: "Cannot GET /api/users" error. What if I wanted to show a custom error? For example: "No web token provided. Register to authenticate" etc etc? Any help is much appreciated. Thank you in advance.
I now have a function that checks for the token before serving a page. It seems to work for now. Currently I am passing the token manually in postman as a header: x-access-token. How would I catch the token upon generation and automaticly make the client send it on future requests? Here is the function that checks for the token and the protected route.
Great. I kept working while waiting for any answers and completed this step. I can now generate the token and using postman pass that to a secured route I created. It works perfectly, but I am struggeling to understand how I am going to save the token on the client side and pass that on every request. I still generate the token, the same way as above. I can verify the token by manually passing it in my header as x-access-token, but how would I do this automaticly?
Update
Here is the function that checks the token and a protected route that utilizes that function:
// Routes - Define routes here
function getToken(req, res, next) {
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
console.log(decoded);
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}
router.get('/entries', getToken, entryController.get)
I found this question save-token-in-local-storage-using-node Which solved the last piece of the puzzle.
Upvotes: 1
Views: 689
Reputation: 1705
You can simply write a middleware for this kind of purpose. Clients will generally send tokens in header, so that you can get the header information and verify it. Your middleware will be something like this.
module.exports = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
// get the authorization header string
const token = req.headers.authorization
// decode the token using a secret key-phrase
return jwt.verify(token, config.secret, (err, decoded) => {
// the 401 code is for unauthorized status
if (err) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
const username = decoded.username
// check if a user exists
return User.findOne({username: username}, (userErr, user) => {
if (userErr) {
return res.status(500).json({
success: false,
message: "Error occured while processing. Please try again.",
err: userErr
})
}
if ( !user ) {
return res.status(401).json({
success: false,
message: "You are not authorized for this operation."
})
}
return next()
})
})
}
For the security reasons it is better to store JWTs in your application associated with the user. Complete explanation can be found here.
Update: You can save the token in cookie and parse the cookie to find out the token and then verify that.
Upvotes: 1