Best way to handle API calls from frontend - node.js

Okay, so atm i have a frontend application built with Nuxt JS using Axios to do requests to my REST API(separate).
If a user does a search on the website the API URL is visible in XMLHttprequests so everyone could use the API if they want to.
What is the best way of making it so that only users that search through my website gets access to the API and people that just directly to the URL gets denied. I suppose using some sort of token system, but what is the best way to do it? JWT? (Users never log in so there is no "authentication")
Thanks!

IMO, you CANNOT block other illegal clients accessing your
backend as you describe that the official client and other illegal have the same knowledge about your backend.
But you can make it harder for illegal clients to accessing your backend through some approach such as POST all requests, special keys in header, 30-minutes-changed token in header and server-side API throttling by client IP.
If the security of the search API is really important, authenticate it by login; if not, just let it go since it is not in your critical path. Let's focus on other important things.

I'm in the same "boat" and my current setup is actually in VueJs but before even come to StackOverflow I developed a way to actually, the frontend calls the server and then the server calls the API, so in the browser, you will only see calls to the server layer that, the only constraint is that the call must come from the same hostname.
backend is handled with expressJs and frontend with VueJs
// protect /api calls to only be originated from 'process.env.API_ALLOW_HOST'
app.use(api.allowOnlySameDomainRequests());
...
const allowHostname = process.env.API_ALLOW_HOST ||'localhost';
exports.api = {
...
allowOnlySameDomainRequests: (req, res, next) => {
if(req.url.startsWith('/api') && req.hostname === allowHostname) {
// an /api call, only if request is the same
return next();
} else if (!req.url.startsWith('/api')) {
// not an /api call
return next();
}
return res.redirect('/error?code=401');
},
...
};
In our case, we use Oauth2 (Google sign through passportJs) to log in the user, I always have a user id that was given by the OAuth2 successful redirect and that user id is passed to the API in a header, together with the apikey... in the server I check for that userid permissions and I allow or not the action to be executed.
But even I was trying to find something better. I've seen several javascript frontend apps using calls to their backend but they use Bearer tokens.
As a curious user, you would see the paths to all the API and how they are composed, but in my case, you only see calls to the expressJs backend, and only there I forward to the real API... I don't know if that's just "more work", but seemed a bit more "secure" to approach the problem this way.

Related

In an Express.js server, how can I send an HTML (with style and js) acquired from a HTTP request, as a response?

This is an Express.js server. I'm trying to authenticate my Instagram API.
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const axios = require('axios');
const ejs = require('ejs');
var app = express();
// bodyparser middleware setup
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var instagramClientId = '123123';
app.get('/instagram', (req, res) => {
axios({
method: 'post',
url: `https://api.instagram.com/oauth/authorize/?client_id=${instagramClientId}&redirect_uri=REDIRECT-URI&response_type=code`,
}).then((response) => {
res.send(response.data);
console.log(response.data);
}).catch((e) => {
console.log(e);
});
});
// port set-up
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`app fired up on port ${port}`);
});
This is the error I got. Looks like the html file was sent just fine, but the css and js weren't executed. You can see the error messages were all about style and js not being excuted.
Is this because I need to enable some options in my res.send() code?
I'm going to answer two questions here, the first is what I think you're actually having problems with and the second is what would technically be the answer to this question.
What I think your actual problem is: A misunderstanding of OAuth2.0
Looking at your code, it looks like you're trying to get a user to authenticate with Instagram. This is great, OAuth2.0 is fantastic and a good way to authenticate at the moment, but you've got the wrong end of the stick with how to implement it. OAuth2.0 is about redirects, not proxying HTML to the user. In this case it looks like you're using axios to make a server side call to the instagram OAuth endpoint and then sending the user that HTML. What you should actually be doing is redirecting the user to the Instagram URL you've built.
A high level of the "dance" you go through is the following.
The user requests to login with instagram by pressing a button on your website.
You send the user to an instagram URL, that URL contains your applications token plus an "approved" redirect url. Once the user has logged in with Instagram, Instagram will redirect the user to your approved redirect url.
The users browser has now been redirected to a second endpoint on your server, this endpoint recieves a one-time token from Instagram. You take that token on your server side and use axios (or similar) to make a server side request to fetch some user information such as their profile. Once you have that data, you can then create a user in your the database if needed and issue a new session token to them. Along with the profile call on this, you'll also get a token given directly to you (different from the one the users browser gave you) which will allow you to make requests to the Instagram API for the privileges you requested from the user originally.
This means you have 2 endpoints on your service, the "hello, I'd like to log in with instagram, please redirect me to the instagram login page" and then "hello, instagram said I'm all good and gave me this token to prove it, you can now check with them directly" (this is the callback endpoint).
You can manage this whole process manually which is great for understanding OAuth, or you can use something like Passport.js to abstract this for you. This lets you inject your own logic in a few places and handles a lot of the back and forth dance for you. In this instance, I'd probably suggest handling it yourself to learn how it all works.
Ultimately, you are not sending the user any HTML via res.send or anything similar. Instead your first endpoint simply uses a res.redirect(instagramUrl). You also thus do not make any HTTP requests during this portion, you do that on the "callback" after they've entered their username and password with Instagram.
Technically the correct answer to this question: proxy the JS and CSS calls, but this is really bad for security!
You're sending some HTML from a 3rd party in this case. So you will need to also allow the user access to the HTML and CSS. Security wise, this is quite iffy and you should really consider not doing this as it's bad practice. All of the JS and CSS links in the page are most likely relative links, meaning they're asking you for some JS and CSS which you are not hosting. Your best bet is to find these exact paths (ie: /js/app.min.js) and to then proxy these requests. So you'll create a new endpoint on your service which will make a request to instagrams /js/app.min.js and then send that back down with res.send.
Again, please do not do this, pretending to be another service is a really bad idea. If you need something from instagram, use OAuth2.0 to authenticate the user and then make requests using their tokens and the official instagram API.

Securing Nodejs express API

I am making a very basic REST API using Express 4 (express.Router). How can I secure this API in a simple way?
I do not need to authenticate individual users so most of the strategies that I have found seem like overkill. I am just trying to moderately secure the API so I am hopefully the only one using it. Not sure what strategy I can use to pass something from the client to identify myself. Thanks!
I will be accessing this API from a web page as well as mobile client (with Ionic) and hosting the app on Google Clouds App Engine.
You could use a header or a query string to authenticate. For example, setup a middleware on Express to check if the header X-TOKEN contains a string of your choice.
Here's a code which you can use to do that:
function checkAuthHeader (req, res, next) {
let token = req.headers['X-TOKEN'];
if (token && token == "RANDOM_GENERATED_STRING") {
next();
} else {
res.status(403).end('Unauthorized.')
}
}
Then you can use this function as a middleware like this (make sure you place it before declaring your routes):
app.use(checkAuthHeader)
You should be using environmental variables instead of a hard-coded string as well. I just used a hard coded one for demonstration.

Protect non-api (res.render) route with express-jwt token in Node.js

First of all, I have read all tutorials on protecting REST API routes with jwt (express-jwt & jsonwebtoken), and it works fine for that purpose.
This works fine:
app.use('/api', postApiRoute);
And this also works, somewhat, I mean.. it does verify the token when I use it to show a webpage with angular http request calls, but when you add expressJwt({secret: secret.secretToken}), you cannot just access localhost:3000/api/post anymore. The expressJwt({secret: secret.secretToken}) is the problem here.
app.use('/api', expressJwt({secret: secret.secretToken}));
app.use('/api', userApiRoute);
What I really need is to protect a non-json but html/text request route with jwt like eg.:
app.get('/admin*', expressJwt({secret: secret.secretToken}), function(req, res){
res.render('index', {
//user: req.session.user, <- not sure how to do the equivalent, to extract the user json-object from the express-jwt token?
js: js.renderTags(),
css: css.renderTags()
});
});
.. without having to make http requests in angular/js, but using express' render function.
I need to do this since my application has 2 primary server routed views, so 1 where admin scripts are loaded from, and 1 where the frontend (theme) assets gets loaded.
I cant however get jwt/tokens to work with server rendered views, only json api requests.
The error i'm getting is: "UnauthorizedError: No Authorization header was found"
Couldn't find any information about (server rendered views protected with jwt, only serverside api requests and client side angular/ajax http requests) this, so I hope my question is clear, and that I do not have to fall back to using sessions again.
Not sure if I understood correctly, but if you are talking about entry html routes (i.e., loaded directly by the browser and not by you angular app), then you simply have no way of instructing the browser as to how to set the authorization header (no without introducing some other redirect based auth flow).

Prevent users from accessing subdomain in Express

I am very new to web development and Node.js / Express. I have an Express server. In my client I send a GET request for some data in the DB. The GET request is handled by
app.get( '/pathname', controller.getsomedata );
The problem is, the user can now type in the URL domainname.com/pathname in the browser and get directed to that pathname. If they add in a certain queries domainname.com/pathname?query, they are able to retrieve data from the DB (this is supposed to happen), but I would prefer if GET requests to /pathname only occur from within the client code, not when the user enters it in the browser.
Is there a better way to do my GET request? Or is there a way to restrict users from accessing /pathname.
I apologize for the newbie question, but I don't know how to word it well enough to do a google search for the solution. Thank you!
It's impossible to do that. If your client-side code is able to access something, malicious user can do that as well.
You can mitigate the issue by using custom HTTP header or something like that, but it's better to validate all data on the server-side.
Allow whole client request as DB query may cause security issues. So be sure to validate query parameters and use them as DB query conditions.
If you want to query DB freely from HTTP query parameter, you should prepend authentication/authorization to the route.
app.get( '/pathname', function(req, res, next) {
if (confirmThisRequestIsFromMe(req)) {
next();
} else {
res.send(401);
}
}, controller.getsomedata );

Cross domain Sails.js + Socket.io authorization

I'm working in an application which delivers push content to a group of web applications hosted in different domains. I'm using Sails.js and Socket.io, and structured it like this:
The client script, running on each web application's client's browser, is something like:
socket.on('customEvent', function(message){
//do something with message on event trigger
}
And then, in the server, the event 'customEvent' is emitted when needed, and it works (e.g. on the onConnect event: sails.io.emit('customEvent',{message ...}).
But I'm facing a problem when it comes to handle authorization. The first approach I've tried is a cookie-based auth, as explained here (by changing the api/config/sockets.js function authorizeAttemptedSocketConnection), but it isn't a proper solution for production and it isn't supported in browsers with a more restrictive cookie policy (due to their default prohibition to third-party cookies).
My question is: how to implement a proper cross-browser and cross-domain authorization mechanism using sails.js, that can be supported in socket.io's authorization process?
======
More details:
I also tried adding a login with a well-known oauth provider (e.g. facebook), using this example as a base. I've got the Passport session, but I'm still unable to authenticate from the client script (it only works in pages hosted by my node app directly).
A JSONP request to obtain the session might be a solution, but it didn't work well in Safari. Plus I prefer the user to be authenticated in one of the web apps, rather than in my node application directly.
I believe your routes must handle CORS mate. Example:
'/auth/logout': {
controller: 'AuthController',
action: 'logout',
cors: '*'
},
Of course you can specify the list of ip your are accepting (and then replace the '*').
Worth mentionning that you must specify where socket.io has to connect to (front-end JS):
socket = io.connect(API.url);
For any common http GET/PUT/POST/DELETE, please ensure that your ajax envelope goes with the credentials (cookie). For example with angular:
$httpProvider.defaults.withCredentials = true
Let me know how it goes.

Resources