How to secure client http calls to the backend? - reactjs

I am trying to figure out what is the best way to prevent random people access/make requests to the server.
Currently, everyone that have the url and the endpoint can easily make requests and I believe it's kind of security breach.
I am using Express.js that hosting static build of React.js
Here is an example of a call:
// Client:
async function getData(id){
try {
const res = await axios.get(`${backendDomain}/data/${id}`)
const data = res.data
return data
} catch (error) {
...
}
}
// Server:
app.get('/data/:id', function(req, res) {
...logic
res.send(data);
});
I tried adding to the Client "x-api-key" header and pass an api key that only I have and add a middleware that will check the api and see if it passed currectly from the client. But obviously it is not a good solution because you can see the key on "Network" section while inspecting.
What can I do?
Looking for the best way to prevent random people accessing the data

Related

AJAX Express receive array on client

I am having trouble with receiving array data as it flows from another source into my client. My goal is to have the HTML document populate with data from the array as it is received by the server.
Server:
const keywordsList = [];
const keywordsListSerialize = JSON.stringify(keywordsList);
const arrayToString = JSON.stringify(Object.assign({}, keywordsList))
// const keywordsListObject = JSON.parse(arrayToString);
app.use(cors())
const bodyParser = require('body-parser');
app.get('/', (req, res) => {
// res.writeHead(200, {
// 'Connection': 'keep-alive',
// 'Content-Type': 'text/event-stream',
// 'Cache-Control': 'no-cache',
// });
//res.flushHeaders();
res.send(keywordsList);
//res.write(arrayToString);
//res.status(200).send(arrayToString);
//res.status(500).send({ error: 'something blew up' })
});
As you can see from the server code, I have tried multiple variations of sending data as a JSON object/just an array or a string and I can't seem to get ANY of it to even show up on my client.
Client:
var xhttp = new XMLHttpRequest();
console.log(xhttp);
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(xhttp.responseText)
var jsonObj = JSON.parse(xhttp.responseText);
for(i = 0; i < jsonObj.length; i++) {
document.getElementById("keywordsList").innerHTML = jsonObj;
}
}
};
xhttp.open("GET", "/", true);
xhttp.send();
The client does two very basic but interesting things. 1. the xhttp.responseText is the whole HTML file which makes me think I should be handling the request somehow in my server (I figured I could get away with just constantly streaming data to the client) and 2. Sends me an error
VM812:1 Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequest.xhttp.onreadystatechange
I believe I am having multiple issues. I understand that I will want to send data almost definitely as a JSON object and parse it on the client but at the most basic examples, I can't even get that to work. I have double checked that how I have my client and server setup is correct and if need be, I can discuss how it's setup as to make sure they are linked correctly locally.
I am looking for guidance as well as possibly a technical answer. Please do not just link me ajax documentation because I have probably read it 3 times over (minimum) at this point. I thought of just throwing everything away and using a websocket as it would accomplish what I am trying to achieve but that means learning all of that.
Thank you for your time.
So I figured it out eventually. The only post above from someone else is correct. It's that simple to send data across. Unfortunately, when trying to understand AJAX and express at the same time, you get confused on whether you are really sending data AND if that data is structured correctly so the client doesn't just throw out errors. I was conflating AJAX and express issues together which could have been resolved quicker had I understood how to correctly test stuff but alas, I am by myself working this all out.
For any poor sap who might come across this, probably don't use XMLHttpRequests and just use JQuery.
Thanks for the response.
Server:
const express = require('express')
const app = express()
const port = 3000
const keywordsList = ['apple', 'banana', 'cucumber']
app.get('/', (req, res) => {
res.send(JSON.stringify(keywordsList))
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Testing the web server:
curl http://localhost:3000
["apple","banana","cucumber"]
To iterate over the response in the client:
const arrayOnClientSide = JSON.parse(xhttp.responseText);
arrayOnClientSide.forEach(element => console.log(element));
which outputs:
apple
banana
cucumber
Note that every time you call .innerHTML you will overwrite whatever value it previously held, so construct the presentation format of your display value first, then assign it to innerHTML at the end.

How can I export static HTML pages from next.js when they need data from a third-party API?

I’m using next.js to build static HTML webpages.
One of my webpages needs data from a third-party API, which I’d like to fetch at build time and bake into the resulting HTML.
I don’t want this call to ever happen on the client, because:
CORS prevents the request from succeeding anyway
I would have to expose an API key on the client (no thank you)
I thought getInitialProps was the answer, because the fetched data is indeed baked in during the build/export process, but when I navigate away from the page and return from it, getInitialProps gets triggered on the client, breaking everything.
My current code in getInitialProps is something like:
static async getInitialProps(){
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
}
Any advice or best practice on how to handle this? I know Gatsby.js does it out of the box.
one possibility would be, if you just want to execute this once on the server to check if the req parameter is present in getInitialProps: (Documentation)
req - HTTP request object (server only).
One dirty approach:
static async getInitialProps({ req }){
if (req) {
// only executed on server
// Get Behance posts
const behanceEndpoint = `https://www.behance.net/v2/users/${process.env.BEHANCE_USERNAME}/projects?api_key=${process.env.BEHANCE_API_KEY}`
const behanceRes = await fetch(behanceEndpoint)
let behancePosts = await behanceRes.json()
// Return only the required number of posts
return {
behancePosts: behancePosts
}
} else {
// client context
}
Hope this helps a little bit.

Meteor redirect client from server-side method

Meteor 1.6, React, React Router
Interfacing with Paypal billing-agreements
Client onClick event:
subPayPal(){
Meteor.call('paypal.getToken', this.props.user_id, (error, response) => {
if(error) {
console.warn(error);
}else{
console.log(response);
}
}
}
I'm calling the method on the server rather than on the client because I'm dealing with privileged info (usernames, passwords, tokens, etc). No problem here.
Server methods:
Meteor.methods({
'paypal.getToken': function getOauthToken(uid){
// simplified a bit
// check if current token valid
// set vars here, then go get token
axios.post(ppAPI, data, config)
.then( function(response) {
// save oAuth token
})
.catch( function(error){
// stuff here
})
// prepare another axios.post(), with oauth Token, to get a payment token
// and approval_url and execute_url from paypal
// call axios.post() and use data in response:
// with the approval_url in this reponse, I need to redirect
// the browser to the approval_url on paypal.com so that the user
// can sign into paypal, and ok the subscription agreement.
// once user 'ok' in paypal, the browser comes back to my site,
// where I render a 'cart' with a final 'ok, purchase' button.
return approval_url;
}
})
So, once I have the approval_url, I can send it back to the client, and when the client "sees" it, it can then call the React Router to the paypal.com site.
PROBLEM
The client's onClick method is obviously async and as soon as I click the initial onClick(), the console.log outputs undefined for process which makes perfect sense.
TRIAL 1
So, I tried using Meteor.apply in the client trying to make it synchronous, waiting for the server-side method to return the approval_url to no avail:
subPayPal(){
Meteor.apply('paypal.getToken', [{uid:user_id}], {
onResultReceived: (error, response) => {
if(error) console.warn(error.reason);
if(response) console.log('server response', response);
}
});
}
I also tried Meteor.call('paypal.getToken').then({ console.log(response) }).catch... to no avail either, as it is still async.
I've also tried try/catch blocks in the server-side method, to no avail. Whatever I try, the server-side code always runs as expected (with exception of the Meteor.call().then().catch() which just plain failed). I just can't seem to promise it, or return it.
TRIAL 2
The next thought would be not caring about the response on the client, if I could get the server-side method, with approval_url defined, to somehow call the React Router on the client and push the unique approval_url to it, but that doesn't make much sense to me how to wire that up.
THOUGHT 1
I guess I could use some "temporary" database collection, which is reactive, so that when the server-side method completes, it'll update (or insert/create) a document, and when the client 'sees' that, it could then call the React Router to redirect the browser to paypal.com. The collection document would have to hold the approval_url URI, which would then be passed down to the client. Not too sure how I'd wire the client to tell the Router when it sees the approval_url "appear".
Sooooo....
Any ideas? Is there more than one solution (and if so, what would be the best?).
I read somewhere that the app should logout the user, then the Router could redirect to paypal, but that doesn't help, as paypal.com redirects back to me, and I wouldn't want the user to have to log back in again.
THANKS.

RXJS sequence for multiple observables

I am trying to wrap my head around RXJS and had a quick question on how to tackle the following workflow using observables instead of promises.
Here is the log in workflow in angular 2:
i look in local storage for JWT. if JWT exists i return the token.
if not in local storage i check to see the platform. if Android i log into my server using google bearer token, my server returns a JWT (through angular HTTP obserable).
if not in local storage and the platform is a windows computer i generate a JWT on my server and return (through angular HTTP obserable)
for the two calls to the server i want to cache the token in local storage before returning the JWT information to the calling function.
I have everything execpt how to do this properly using RXJS, mostly how to chain all of this together. Can i get a quick pseudocode using Observables on how to do this? I basically want to intercept the JWT and store in local storage before moving on in the application (the calling function subscribing to the sequence above)
Any help would be great!
I would prefer async functions for this task.
var jwtPromise = null;
function getJwt() {
return jwtPromise || (jwtPromise = getJwtNotCached());
}
async function getJwtNotCached() {
const localJwt = getLocalJwt();
if (localJwt) {
return localJwt;
}
const newJwt = await fetchJwtByPlatform();
storeLocalJwt(newJwt);
return newJwt;
}
async function fetchJwtByPlatform() {
if (platformIsAndroid()) {
return await fetchJwtOnAndroid();
}
return await fetchJwtOnWindows();
}
...
This code will even ensure that no multiple network requests are made if called twice at a time.

How do I call a mongoose Schema function with Restangular?

I'm new to MEAN development, and I'm using bcrypt to encrypt the password from UserSchema (everything works wonders), but since I have to authenticate from the client side I've made a function to the UserSchema:
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
Now.. how do I call this function from the client side?
I'm using Restangular, and I'm trying something like this:
function authLogin() {
if(vm.user && vm.user.email && vm.user.password){
User.getList({ email: vm.user.email }).then(function(user){
user.comparePassword(vm.user.password, function(err, isMatch) {
if (err) throw err;
console.log(vm.user.password, isMatch);
});
});
}}
But then occurs an error saying that user doesn't have the funciton:
user.comparePassword is not a function
So, what is wrong about it? Or it is not possible to call a Schema function directly from the client side?
I believe the answer to your immediate question (if i understand which of your code samples lives to which layer): why is it "not possible to call a Schema function directly from the client side" is that the server and client side javascript run in a completely different context so functions you might declare in server-side models or middleware are not available to your client context.
The general solution to "call a schema function" from the client side with node and javascript frameworks is to POST or GET data to a server-side API endpoint and then you call your schema methods within the API code (or middleware attached to that API endpoint).
I would suggest that you take a look at the https://www.npmjs.com/package/passport passport authentication API module which is a market standard way to handle client authentication that you should be able to pretty easily bolt onto your application.

Resources