Stripe webhook security - verify IP address in addition to signature? - next.js

I'm using webhooks in a Next.js app to handle incoming customer payment events from Stripe. Similar to the example in the docs, the first thing my webhook does is verify the signature of the incoming event:
const webhookSecret = process.env.SECRET_STRIPE_WEBHOOK
const webhookHandler = async (req, res) =>
{
if (req.method === 'POST')
{
const buf = await buffer(req)
const sig = req.headers['stripe-signature']
let event
try
{
event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret)
}
catch (error)
{
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
// On error, log and return the error message
if (error instanceof Error) console.log(error)
console.log(`Error message: ${errorMessage}`)
res.status(400).send(`Webhook Error: ${errorMessage}`)
return
}
// The rest of the webhook
}
}
However, the docs also mention to only trust events coming from these IP addresses, and provides a list of IP addresses from which webhook events may originate.
For security purposes, is it necessary to also check the IP address of the incoming event, or does the above implementation of verifying the signature cover all bases? The docs explain that verifying signatures through Stripe's libraries does protect against replay attacks, but they do still mention verifying IP addresses in the same sentence.
If verifying IP addresses is a necessary or prudent thing to do, how can I add that to the above implementation? I wasn't able to find examples on the Stripe docs.

Verifying the IP address is an additional layer of security to guarantee the request comes from Stripe. Years ago it was the best approach as webhook signatures didn't exist but since they shipped this feature I think most developers have defaulted to that approach. An attacker can't calculate the signature themselves though they could find a way to replay the requests. I would say most integrations do not bother with the IP allow list.
If you were to implement this, you'd need to look at the IP address associated with the request in your route server-side. This is done with NextRequest though there are other solutions in this answer.

Related

Firebase emulator: see outgoing HTTP traffic

I have a Cloud Function that calls to Chargebee. In index.ts:
const chargeBee = new ChargeBee();
...
chargeBee.configure({
site,
api_key: apiKey
});
...
export const finalizeSignup = https.onCall(
async (info: SignupInfo, ctx: CallableContext) => {
const cbCmd = chargeBee.hosted_page.retrieve(info.cbHostedPage);
const callbackResolver = new Promise<any>((resolve, reject) => {
// cbCmd.request returns a Promise that seems to do nothing.
// The callback works, however.
// Resolve/reject the Promise with the callback.
void cbCmd.request((err: any, res: any) => {
if (err) {
reject(err);
}
resolve(res);
});
});
// Calling Promise.resolve subscribes to the Promise.
return Promise.resolve(callbackResolver);
}
);
I am testing this function using the Firebase emulators, started via firebase emulators:start --only functions. Chargebee is responding strangely. They require the domain of their incoming requests to be whitelisted: my first guess is that the domain being used by my locally emulated Cloud Function is not whitelisted on the Chargebee side.
How do I see outgoing HTTP information sent by my locally emulated Cloud Function?
The connection is actually HTTPS, not HTTP.
The emulators provide no functionality to intercept network traffic of any form.
For HTTP: you have to apply your own tooling to monitor the HTTP traffic (ie Wireshark).
For HTTPS: possible to monitor using Wireshark, but impossible to analyze without knowing the SSL key. And in the setup above, where a third-party library is handling the request, there is currently no way to obtain the SSL key. I entered a feature request with Firebase to gauge the interest of developing a way to define an SSL key log when starting the Functions emulator, similar to Chrome. A user only identifying themselves as 'Oscar' told me in a private email that "I've already filed a feature regarding this topic to our engineering team regarding this matter, which will be discussed internally." So that tells us that (1) Firebase is aware that the feature is currently lacking, and (2) there is no progress to report on the feature.

Are there any port(s) & IPs I need opened in my private server's firewall for Cloud Functions requests to go through?

I have a Google Assistant Action that uses Cloud Functions and in that Action I need to send GET/POST requests to my private server.
We're using Thingworx platform for development and it is hosted on a private cloud behind a firewall.
I've also tried Postman to send these requests and it worked perfectly, but when I use the same on my Google Assistant Action, it just doesn't work and all I get is an empty response.
My request code is below:
function callMeAPI(agent) {
var request = require("request");
var options = { method: 'GET',
url: 'https://{IP:Port}/Thingworx/Things/{Path}',
qs: { appKey: 'AppKey Used Here' },
headers:
{ 'cache-control': 'no-cache',
Accept: 'application/json' } };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
agent.add("body");
});
So, are there any ports and IPs/URLs we need to add to our firewall rules to add for requests originating from Cloud Functions to go through the firewall onto our Thingworx Application?
You should not make any assumptions about the IP address of outbound traffic originating from Cloud Functions. The originating IP could change over time, depending on Google infrastructure, and also based on which region your function was deployed to.
If you need to ensure that access to your network originated from your function, you should implement some sort of authentication in the request so that your own application can validate the request. Typically, there is some shared secret that only both sides know that indicate they are aware of each other.

Is it possible to determine the source of an incoming request?

Is it possible to know the hostname of the source of an incoming request to a cloud firestore document? I would like to write a database rule of the form allow write: if request.resource.data.source_host_name == some_predefined_value. This is a web application so I'm trying to find a good way to limit who gets to write to my database without using traditional auth methods.
That sort of rule is not possible with Cloud Firestore. It also wouldn't be very secure, as it's possible to spoof source IP addresses.
If you want to limit who can access your database, the only supported mechanism is through security rules and Firebase Authentication.
Here's my solution to logging a client's IP address when interacting with Firestore. I agree with Doug that this won't guard against IP spoofing. Nonetheless, it's helpful for my purposes.
The trick is to create intermediary API endpoints using Cloud Functions, which then interact with Firestore. Here's an example Google Cloud function.
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
exports.yourEndpoint = functions.https.onRequest(req, (res) => {
// Get the IP address from the headers in the request object
const ipAddress = getIPAddress(req);
// Do whatever you need in your firestore DB with ipAddress
admin.firestore().collection('ipAddresses').add({address: ipAddress})
.then((writeResult) => {
return res.status(200).send();
})
.catch((error) => {
return res.status(500).send();
});
});
// Helper function to extract the IP address from request headers
function getIPAddress(req) {
return (
req.headers["fastly-client-ip"] ||
req.headers["x-forwarded-for"] ||
req.headers["No IP, probably because we are in development mode"]
);
}
Note: you won't get an IP address in development mode, because the Firebase emulators don't create the proper headers.

External use of Meteor method? (to receive SMS from Nexmo)

In my Meteor application I want to receive text messages through Nexmo. How do I create the callback function? I'm thinking of something like
Meteor.methods
'receive_sms': (values) ->
console.log values
But http://hitchticker.meteor.com/receive_sms doesn't really work of course. I can see my method is working when I do Meteor.call('receive_sms', 'test') in my browser, but the network debugger is not really giving me a lot of useful information. The Meteor docs aren't very helpful either.
How do I access the method from elsewhere?
Iron Router and then server side routes. Something like:
Router.route('/download/:file', function () {
// NodeJS request object
var request = this.request;
// NodeJS response object
var response = this.response;
this.response.end('file download content\n');
}, {where: 'server'});
In order to receive sms from nexmo you should make the callback (incoming url) available over the internet. Nexmo won’t be able to call localhost to send the incoming sms messages.
Here are some resources to tunnel request over the internet to localhost.
https://ngrok.com/
http://localtunnel.me/
https://pagekite.net/

Storing client's IP address in Firebase

As far I know there is no information available from the browser to find out the client's IP address without making another call to another resource.
Is there a way to store a client's IP address? I mean in a similar way as the firebase.timestamp placeholder works.
Although I've heard that there are now ways for clients to determine and report their IP address, relying on client side code to report their IP address also opens up the ability for anyone to run custom client side code to report the wrong IP address.
The only secure way of keeping track of the clients' IP addresses would either involve you having a server, or Firebase having a special function, when called by the client, that causes firebase's server to grab the client's IP address and save it for you.
My recommendation would be to run a simple server that can take a POST request. The server only needs to be able to verify what user the request is coming from, and will be able to accurately grab the client's IP address and save it on firebase.
in the following URL
https://firebase.google.com/docs/auth/admin/manage-sessions
In section "Advanced Security: Enforce IP address restrictions"
you'll find a reference to the remoteIpAddress
in this piece of code (the row that the arrow points to):
app.post('/getRestrictedData', (req, res) => {
// Get the ID token passed.
const idToken = req.body.idToken;
// Verify the ID token, check if revoked and decode its payload.
admin.auth().verifyIdToken(idToken, true).then((claims) => {
// Get the user's previous IP addresses, previously saved.
return getPreviousUserIpAddresses(claims.sub);
}).then(previousIpAddresses => {
// Get the request IP address.
const requestIpAddress = req.connection.remoteAddress; <=============================
// Check if the request IP address origin is suspicious relative to previous
// IP addresses. The current request timestamp and the auth_time of the ID
// token can provide additional signals of abuse especially if the IP address
// suddenly changed. If there was a sudden location change in a
// short period of time, then it will give stronger signals of possible abuse.
if (!isValidIpAddress(previousIpAddresses, requestIpAddress)) {
// Invalid IP address, take action quickly and revoke all user's refresh tokens.
revokeUserTokens(claims.uid).then(() => {
res.status(401).send({error: 'Unauthorized access. Please login again!'});
}, error => {
res.status(401).send({error: 'Unauthorized access. Please login again!'});
});
} else {
// Access is valid. Try to return data.
getData(claims).then(data => {
res.end(JSON.stringify(data);
}, error => {
res.status(500).send({ error: 'Server error!' })
});
}
});
});

Resources