I'm currently working on a MEAN full stack web project for a little marketplace app. This project's build in 3 parts :
- server -> node.js express mongoose
- front web -> angular 4
- front mobile -> ionic
I've to build this simple API REST, with a classic CRUD, but I have to use JWT to secure my adverts (for the market place).
An user will be able to delete or modify only the advertisements which he himself create, using JWT verification.
Currently, I have a token verification's middle-ware, but it does not prevent a user from deleting an ad created by another user.
I'm calling my middle-ware as I understood on tutorials, it can be change.
And after a lot of research, i only found information about authentication with JWT, then if someone can help my, thanks.
//my token verification's middle-ware
function verifyToken(req, res, next) {
var token = req.headers['x-access-token'];
if (!token)
return res.status(403).send({ auth: false, message: 'No token provided.' });
jwt.verify(token, config.secret, function(err, decoded) {
if (err)
return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
// if everything good, save to request for use in other routes
req.userId = decoded.id;
next();
});
}
//an example of middle-ware call
router.delete('/:id',VerifyToken, (req, res) => {
advertModel.findById(req.params.id, (err, advert) => {
if(!advert){
res.json({message:"No advert corresponding"})
}
advert.remove((err) => {
if(err){
console.log(err);
}
res.json({message: 'Successfully deleted'});
});
});
});
This application is still under construction, then if you have any comments that would allow me to improve the few pieces of code that you see, go.
jwt token when comes with a request, if that token is valid it just pass the request to next with that user credential, but if the token is not valid, it stops the request lifecycle. But it does not have anything to do with the things you are trying to do.
You can write a simple conditional in your controller code like this
if(req.user.id !== youradd.user_id){
return ('with valid message and http code')
#shahinmahmud is right. Basically there are two parts to what you are doing. Authentication and Authorization. Authentication is done by JWT token validation. Authorisation is to restrict access based on the user. In your case, if it's just access to one resource, a simple if-else will do. Otherwise you need to look into some user management libraries.
This definition should probably help
Related
I'm developing a program which consists of a back-end server, a mobile application, and a web application. I have added JWT token to my mobile application and I'm storing in async storage. However, I cannot find an appropriate way to do in web server.
I have created a middleware file for creating and checking token's validity. In my API route's I'm doing the following
router.post('/:url', middleware.checkToken, (req, res, next)=>
{
...
}
So, every time I call this API, middleware file checks for the token. In my mobile application, I'm storing the token in the async storage and pass it to the web server.
However, in the browser side, I want to store the token inside a cookie rather than storing in local storage. How can I do this without changing my code?
This is mobile login API.
router.post('/login', (req,res,next) => {
let username = req.body.username;
let password = req.body.password;
User.findOne({'username' : username})
.exec()
.then(doc =>{
if(doc.validPassword(password))
{
let token = jwt.sign({
id: doc.id,
email: doc.email,
},
config.secret,
{ expiresIn: '24h' // expires in 24 hours
}
);
res.status(200).json({
success: true,
message: 'Authentication successful!',
token: token
});
}
else{
// invalid credentials
res.send(403).json({
success: false,
message: 'Incorrect username or password'
});
}
})
})
I don't want a separate file for web login. I just want to use the same code, without copying to another file.
Should I write another different code for both mobile and web but one send a cookie, other sends plain token? Is there any way to achieve this with simple solution?
In short:
Mobile users send credentials to the mobile login page and they receive token.
Web users send credentials to the web page and they receive a cookie (a token resides inside the cookie). I don't want to have separate code for login.
You can easily add a new cookie in the front-end with document.cookie = ... (MDN document cookie)
In your middleware, you just have to parse for cookie instead of some Bearer token or whatever.
I was attempting to build a signup/signin application using Nodejs. By looking at the articles, I came to know that it's a good structure to design your app so that your backend is actually a RESTful API and your client accesses that API. (Both client & server running on different servers, whereas the client is just a plain old static file server).
Now things went smooth until I had to sign users in. When the API endpoint (/signin) with the specific data is accessed, the data is validated against the database and if Okay, I am signing a JSON Web Token and passing it along to the client.
But the problem is that with this, I can only secure routes on my API i.e. I can only enforce that a user must be signed in to access a specific backend API endpoint.
But what can I do to enforce the same thing on my client with this JWT? For example, if in my client I have dashboard.html and I want it only accessible to signed in users, a user can go ahead and get a JWT generated. But how does this JWT come into play about restricting client routes?
My Signin Route:
app.post('/signin', (req, res) => {
var data = req.body;
if (!exists(data.username) || !exists(data.password))
return res.status(422).json({
message: 'All fields are required'
});
const users = db.get('users');
users
.findOne({
username: data.username,
password: shajs('sha256').update(data.password).digest('hex')
})
.then((user) => {
if (user) {
jwt.sign({
_id: user._id,
username: user.username
}, 'keyboard_cat', {
expiresIn: '1h'
}, (err, tok) => {
return res.status(200).json({
message: 'OK',
token: tok
});
});
return;
}
return res.status(200).json({
message: 'Invalid Username or Password.'
})
});
});
You can use conditional render on the front-end side. You can fire an api with the api token (generated from '/signin' api) which will tell you wether the api token is valid or not whenever you enter the route.
On the basis of the response from the server about the token you can decide which page to render (normal one or the unauthorised page).
There is also a better approach, send the api token in every route in the header, and if the token is malformed or invalid return 401 error from backend. Catch this error globally(or you can use response interceptor which is provided by axios) and then do conditional rendering.
I'm using passport-jwt to authenticate some routes and I'm creating my jwts with node-jwt-simple/jwt-simple but facing some difficulties cause it looks like my passport-jwt authenticate middleware is not being called at all.
Here is my
passport-jwt-strategy
const jwtOpts = {
jwtFromRequest: ExtractJwt.fromHeader('Authorization'),
secretOrKey: secret,
};
passport.use(new jwtStrategy(jwtOpts, (payload, done) => {
console.log('payload ', payload.sub);
User.findById(payload.sub, (err, user) => {
if(err) { return done(err); }
if(!user) { console.log('didnt find!'); return done(null, false); }
done(null, user);
});
}));
which i'm then integrating it over here.
routes file
router.get('/success',
passport.authenticate('jwt', {session: false}),
async (ctx, next) => ctx.body = await "success!");
Here is also the way I make my jwt.
function tokenForUser(user) {
const timeStamp = new Date().getTime;
return jwt.encode({sub: user._id, iat: timeStamp}, secret);
}
//- Later in signup process
userToSave.save(async(err, user) => {
if(err) { return next(err); }
const token = await tokenForUser(user);
next(token);
});
//- If this helps, here is how my secret file looks like.
const secret = "JKAha23ja1ddHdjjf31";
export default secret;
Problem comes, when I hit that route i only get Unauthorized and in the console nothing gets logged out not even the 'payload' key I specified first.
I should also say that I have the token at ctx.request.get('Authorization') (Koa based) i think it's something like req.header('Authorization') with express in all routes.
Also The exact express based problem can be found on the github issues of node-jwt-simple here incase there is any problem with my code samples.
Thank you.
After I wrapped my head right i knew that this has been my horrible understanding of how the whole authentification process works.
When I decoded the token from ctx.get('Authorization') I got a different _id than the one stored in the db Because I had hardcoded Authorization header in postman and thought "If I ctx.set('Authorization', token); It will replace the one I hardcoded on postman".
Less did I think that this jwt will be included in a header of requests when I make http calls on front end.
I naively thought jwts are passed directly from the server to the browser (Something like how render works) and Not from the server to an ajax process which later embeds it in request made which is the correct way.
The whole code is awesome, except now I have to just pass the token ctx.body = token; after I created it when I signed up.
Thank You.
I have a sails.js app that generates API to my client. In order to secure my API I need to implement OAuth2.0 to my sails app. I have started to follow this tutorial: https://www.npmjs.com/package/sails-generate-auth#requirements
But I get all kinds of diffrent errors when every time when I try to lift the server. I also dont understand to where i'm suppose to send my credentials to the server and get the access token. I'm fairly new to Sails.js and just got to know OAuth and I can't find a proper guide on how to implement OAuth.
How can I implement OAuth to my app? please have a detailed answer so that I can fully understand.
UPDATE:
ok so instead I started to follow this guide: https://www.bearfruit.org/2014/07/21/tutorial-easy-authentication-for-sails-js-apps/
and I think I got everything to work as it should(?) when I register an account it saves the data in the database as it should. The login also seems to work properly But I didn't understood how I can access the actuall data like the username and email address after the login redirects me to the homepage? I've tested the login on postman and when i log in I get a cookie. What am I suppose to do with it?
The AuthController generated by sails-generate-auth doesn't add the user details to the session by default so you should add it manually by adding the following line to the callback function in AuthController.js
req.session.user = user;
This is how the callback looks like with the line:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true;
req.session.user = user;
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/');
});
});
}
You can now use the user details in any of your controllers and views by referring to req.session.user for example twitter provides your user name so you can use req.session.user.username.
I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})