Protect API routes in Node.js - javascript

I have some routes in my Node.js API sending data from a MongoDB database to an Angular 4 frontend.
Example:
Node.js route:
router.get('/api/articles', (req, res) => {
Article.find({}, (err, articles) => {
if(err) return res.status(500).send("Something went wrong");
res.status(200).send(articles);
});
});
Angular 4 service function:
getArticles() {
return this.http.get('http://localhost:3000/api/articles')
.map(res => res.json()).subscribe(res => this.articles = res);
}
The question is, how do I protect my Node.js API routes from browser access? When I go to http://localhost:3000/api/articles I can see all my articles in json format.

This is not a security measure, just a way to filter the request. For security use other mechanisms like JWT or similar.
If the angular app is controlled by you then send a special header like X-Requested-With:XMLHttpRequest (chrome sends it by default for AJAX calls) and before responding check for the presence of this header.
If you are really particular about exposing the endpoint to a special case use a unique header may be X-Request-App: MyNgApp and filter for it.

You can't really unless you are willing to implement some sort of authentication — i.e your angular user will need to sign into the api.
You can make it less convenient. For example, simply switching your route to accept POST request instead of GET requests will stop browsers from seeing it easily. It will still be visible in dev tool or curl.
Alternatively you can set a header with your angular request that you look for in your express handler, but that seems like a lot of work for only the appearance of security.

Best method is to implement an authentication token system. You can start with a static token(Later you can implement dynamic token with authorisation).
Token is just a string to ensure the request is authenticated.
Node.js route:
router.get('/api/articles', (req, res) => {
let token = url.parse(req.url,true).query.token; //Parse GET param from URL
if("mytoken" == token){ // Validate Token
Article.find({}, (err, articles) => {
if(err) return res.status(500).send("Something went wrong");
res.status(200).send(articles);
});
}else {
res.status(401).send("Error:Invalid Token"); //Send Error message
}
});
Angular 4 service function:
getArticles() {
return this.http.get('http://localhost:3000/api/articles?token=mytoken') // Add token when making call
.map(res => res.json()).subscribe(res => this.articles = res);
}

With Express, you can use route handlers to allow or deny access to your endpoints. This method is used by Passport authentication middleware (which you can use for this, by the way).
function isAccessGranted (req, res, next) {
// Here your authorization logic (jwt, OAuth, custom connection logic...)
if (!isGranted) return res.status(401).end()
next()
}
router.get('/api/articles', isAccessGranted, (req, res) => {
//...
})
Or make it more generic for all your routes:
app.use('*', isAccessGranted)

Related

In Nodejs, how to have individual "allowed methods" for each express enpoint?

I am building a rest API with nodejs and express.
I am trying to implement a small CORS system to the endpoints:
cors.js
export const cors = ({ allowedMethods }) => {
return (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
if(!allowedMethods.includes(req.method)){
return res.sendStatus(405);
}
next();
}
}
server.js
const app = express()
app.use(express.json())
app.post('/', cors({allowedMethods: ['POST']}), (req, res) => {
})
app.put('/photo', cors({allowedMethods: ['PUT']}), (req, res) => {
})
Let's say I have these 2 endpoints, every time I go to a URL with an unallowed method, I get a 404 response but I want "405 Not Allowed". (makes sense, because of app.<method>())
How is it possible to look up the allowedMethods for each endpoint and decide what to do next?
I've tried using app.use(cors()), but that catches all endpoints and I'll never know about the allowedMethod specified for a specific endpoint. And I don't think that node cors package would do it.
You're conflating two different things here.
CORS has a mechanism for stating what HTTP methods are allowed to be used to make a cross origin request from JavaScript which is used in combination with a preflight OPTIONS request.
HTTP has a mechanism to say that a request is using an HTTP method that is not allowed at all.
Since you are attempting to generate 405 Not Allowed responses, you are dealing with the latter. This has nothing to do with CORS and should be kept separate from CORS middleware.
all will match all requests for a path, no matter what HTTP method is used, so you can write:
const method_not_allowed = (req, res) => {
return res.sendStatus(405);
};
app.post('/', cors(), (req, res) => { });
app.all('/', method_not_allowed);
Any POST request will get caught by app.post('/' and any other request to / will carry on and get matched by app.all('/').
That said, it looks like you are reinventing the wheel and could just use this existing module.
If you need to also deal with Access-Control-Allow-Methods then you need to take care of that separately.
That does need to be handled with the rest of your CORS logic, and you need to handle both the methods you want requests to be made with (post in this example) and OPTIONS (for the preflight).
If you don't mind a bit of configuration:
const routes = {
"/": {
get: (request, response) => { ... },
post: (request, response) => { ... },
},
"/photo": {
put: (request, response) => { ... },
},
};
app.use((request, response, next) => {
const route = routes[request.url] || {};
if (!Object.keys(route).includes(request.method.toLowerCase())) {
return response.sendStatus(405);
}
next();
});
for (let route in routes) {
for (let method in routes[route]) {
app[method](route, routes[route][method]);
}
}
Even though you'll get in trouble soon with request params (/photos/:photo_id).
EDIT: didn't know about app.all, much cleaner!

Express/axios don't set cookies and headers for response

I have very complicated problem. My project has 2 API's. 1 API is for front-end of my application and 1 is for back-end. How it works, is just I send request from front-end API to back-end API.
Front-end API has URL http://localhost:8080/api/ and back-end API has URL http://localhost:3000.
The problem is front-end API can't get cookies from back-end API.
Here is example, function of front-end API just send request to back-end:
router.get('/test-front-api', async (req, res) => {
const data = await api.get('/test-back-api')
return res.json(data.data)
})
Back-and just sends cookie and some random text:
router.get("/test-back-api", (req, res) => {
res.cookie("test", "cookie")
res.send("Hi from back-end")
})
A here is where I have problem:
router.get('/test-front-api', async (req, res) => {
const data = await api.get('/test-back-api')
console.log(data.headers) // If you do console.log here you will this cookie test
return res.json(data.data)
})
But this cookie is not set in browser.
Let me show one more example. In browser I can just type http://localhost:8080/api/test-front-api and here is result, no cookie:
But, if I type not front-end API endpoint, but back-end, everything works perfect:
I was trying literally everything I found about cors options, axios {withCredentials: true} etc. Nothing works.
I have found one solution, but I don't like it:
router.get('/test-front-api', async (req, res) => {
const data = await api.get('/test-back-api')
// Something like this
res.cookie("test", JSON.stringify(data.headers['set-cookie']))
return res.json(data.data)
})
So, my question is why cookie is no set by front-end endpoint?
I have found not reason of the problem, but solution that seems to be ok. As I said, there are 2 API's - front-end and back-end.
From back-end API I get this cookie, but actually, it makes no sense to send it in header. Why? Because the front-end API will send it back on front.
So, using example above, you can do this, first, just send this cookies in body:
router.get("/test-back-api", (req, res) => {
res.json({cookie: "cookie"})
res.send("Hi from back-end")
})
And then, in front-end API, handle it:
router.get('/test-front-api', async (req, res) => {
const data = await api.get('/test-back-api')
res.cookie("test", data.cookie)
return res.json(data.data)
})
But I still have no idea, why I can send the same headers twice, through back-end API, then front-end API and finally on front.

How to route through a path and perform different functionality at different URLs?

I would like to post at the path /users and then immediately post to /users/:id, but the actions need to be different at each of these URLs, so I can't use the array method for applying the same middleware to different URLs
The idea is that POST(/users/:id, ...) will never be called by the client. It only gets called immediately after POST(/users, ...)
When using express, you are providing a handler function for a specific endpoint. Actually it's an array of those functions (middlewares). That means that you can switch from :
route.post('/users/`, (req, res, next) => {
// do your magic
});
to
route.post('/users/', handleMyCall);
This way you can easily reuse those functions in multiple endpoints without your need to actually make requests:
route.post('/users/', (req, res) => {
// do something +
handleMyCall(req, res);
// either return the result of this call, or another result
});
route.post('/users/:userID', (req, res) => {
// do another operation +
handleMyCall(req, res);
});
Update:
Using GET or POST differs in the way the data is sent to the server. You can use both for your cases, and it really depends on the testing client you have.
Typically, a GET request is done to query the database and not do any actions. POST is usually used to create new entities in the database.
In your scenario, I'd guess you would have post('/users/) in order to create a user. And then have get('/users/:userID') to find that user and return it to the client.
You can easily have different endpoints with different handles for those cases.
As I understood from the comments, you'll need a POST request on /users (to persist data in some database) and GET /users/:id to retrieve these data, which is very different from POSTing the same thing on 2 different endpoints.
POST is generally used to persist and GET to retrieve data.
I'll assume you use some kind of NoSQL DB, perhaps MongoDB. MongoDB generate a unique ID for each document you persist in it.
So you'll have to have 2 routes :
const postUser = async (req, res, next) => {
try {
// persist your user here, perhaps with mongoose or native mongo driver
} catch (e) {
return next(e);
}
}
const getUserById = async (req, res, next) => {
try {
// get your user here thanks to the id, in req.params.id
} catch (e) {
return next(e);
}
}
export default (router) => {
router.route('/users').post(postUser);
router.route('/users/:id').get(getUserById);
};

pass an attr to all good responses in sails js

I'm currently working on a angular + sails project. I'm using json web tokens for auth. It works fine but I wanna set a new token for every validated request that my angular app does.
This is my auth policy
passport.authenticate('jwt', function (error, user, info) {
if (error) return res.serverError(error);
if (!user)
return res.send({
message: info.message,
code: info.code,
tokenError: info.name
});
// The token is ok past this line
// I check the user again
User.findOne({ email: user.email }, function (err, thisUser) {
if (err) { return res.send(err); }
if (!thisUser) {
// send a bad response
}
req.user = user;
// This is the new token that I wanna send to the frontend
var newToken = AuthService.createToken(thisUser);
next();
});
})(req, res);
With this policy I can create the new token, but then I would need a way to include this token in every response, this Is the point where I'm stuck.
I gues I could do it manually in every controller action, but this is want I want to avoid
The best way to standardize your responses in Sails is to use the custom responses feature. In short, instead of calling res.send() or res.json() in your controller actions, call res.ok() instead, and then customize the api/responses/ok.js file that is generated with every new Sails app. This is the same response that Sails blueprints use as well!
In your case, you'd want to save the token onto the request object (e.g. req.token) in your policy code, then use that property in your logic inside of ok.js.

Unexpected behavior when validating a session token using parse.com

I'm trying to build a simple application with parse.com as my user manager.
I would like to make a login call to parse.com from my client side, and call my node.js server with the user's session token (I'll add it as a cookie). In the server side, I'll validate the session (using https://parse.com/docs/rest#users-validating) and allow access only if the session is valid.
For example (in my server):
app.get('/api', function(req, res, next) {
var token = getTokenFromRequest(req);
if(tokenIsValid(token)) {
next();
} else { // Redirect... }
});
app.get('/api/doSomething', function(req, res) {
// Do something....
});
the tokenIsValid(token) function should be implemented using https://parse.com/docs/rest#users-validating.
However, it seems that the REST API user validation returns the user even if the user is logged out (expected to return 'invalid session').
Is this a bug in the REST API user validation? What am I doing wrong? Is there a better way for doing that?
Thanks!
Via REST there's no concept of sessions really. REST calls are meant to be stateless meaning that the (current) user at /me will be serialized from the token provided. If the token is associated to a user it will return the JSON representation of that user otherwise in returns an error.
One way or another that call is asynchronous so you can't really use it in and if statement.
You can do:
app.get('/api', function(req, res, next) {
var token = getTokenFromRequest(req);
serializeUserFromToken(token,function(err,parseResponse) {
if(err) return next(err)
if(parseResponse.code && parseResponse.code === 101){
// called to parse succedded but the token is not valid
return next(parseResponse);
}
// parseResponse is the current User.
next();
});
});
Where serializeUserFromToken makes a request to Parse with the token in the X-Parse-Session-Token header field.

Categories