39

I have an API Key to a paid service. This API is invoked from an unauthenticated page on my site. I am proxying the request to the paid service through my backend server. I have also added CORS on the API to make sure it is called from my site.

THe above protections work when a user is accessing it through the browser. However, the API can be accessed from postman and this could result in me having a huge bill for the paid service. What is the best way for me to ensure that the API is only called from my JS client?

Johnny Donalistic
  • 537
  • 1
  • 4
  • 5
  • 14
    How does **the third party service** prevent such abuse? What **do they recommend** customers to avoid such abuse? – usr-local-ΕΨΗΕΛΩΝ Mar 22 '21 at 12:45
  • 25
    Also remember CORS does not prevent intended DoS calls, as it is enforced only by the browser. CORS has a different purpose: disallow others from *embedding your API* in **their** page – usr-local-ΕΨΗΕΛΩΝ Mar 22 '21 at 12:46
  • 3
    With a lot of third party API's this is done not by the caller but by whitelisting the domains the third party service will respond to requests from (e.g. Google Maps). – Jared Smith Mar 22 '21 at 14:14
  • 2
    How does ensuring it's only called from your JS client solve your problem? What checking does the JS client do to make sure the calls aren't abusive? It is not possible to give a useful answer to your question without understanding *precisely* how your JS client prevents abuse -- because that's what has to be replicated. – David Schwartz Mar 22 '21 at 18:00
  • 1
    If you have any kind of abuse-prevention mechanism in the client-side, you should also enforce it on the server-side. What form of validation are you doing with (client) JS that you cannot do in your backend? – Countour-Integral Mar 22 '21 at 18:33
  • 3
    Let me get this straight... you are paying for a publicly available API... and the host that provides the API is charging per use... and has NO method to restrict or control access call to it? Zero? White listing IP addresses? token exchanges to login? 2FA authentication? NOTHING? I find that hard to believe.. – WernerCD Mar 22 '21 at 21:54

8 Answers8

67

You can't. Even for things that aren't a website, like an embedded device, somebody could always open up the hardware and inspect the firmware or, in an extreme case, de-cap the chips and examine them with an electron microscope. For websites, it's utterly trivial. Anything and everything your client can do, so can any other client. That's inherent in the decentralized nature of the Internet and also in the way web pages work.

There are things you can do to limit abuse, like having your server make the request (rather than the user's browser) and limiting the rate at which it will do so on your users' behalves. Authentication might potentially let you limit requests per user and/or pass along the bill. Actually controlling the client used to talk to your server (or the third-party one) is impossible, though. At best, you can obfuscate your code and try to hide the access key, but in the end, you can't hide it perfectly.

CBHacking
  • 42,359
  • 3
  • 76
  • 107
  • 1
    If the service is intended to be used solely from a particular web page, it may be possible to frequently change some aspects of the API, along with the web page that accesses them. Someone who doesn't know in advance how the web page will change would have no way of designing code beforehand that would accommodate the change to the API without disruption. – supercat Mar 22 '21 at 17:28
  • @CBHacking - the exact scenario is that our sign up form uses an address validation service when customers put in their address. This is a paid service. So abuse could be the API being flooded with requests to use up our credit, but what im more worried about is cbhacking.com using the service for their customers sign up page too. It wouldnt be a DoS or an attack that rate limiting would pick up. – Johnny Donalistic Mar 23 '21 at 02:44
  • 7
    For something like that, your best bet by far is to call the service from your server (not from the user's browser). Somebody who wants to abuse your access can still do so, basically using your server as a proxy to the service, but it's more effort for them, you can occasionally change things to break such abuse, and you can do stuff like monitor the IP addresses that requests come from and block abusers. Anything else requires putting your access key in the web content somewhere, where anybody who wants to can grab it and use it. – CBHacking Mar 23 '21 at 05:00
  • The statement in OP "I am proxying the request to the paid service through my backend server" suggests that they *are* calling it from their server and *not* directly from the user's browser. – GalacticCowboy Mar 23 '21 at 18:59
  • 1
    @JohnnyDonalistic Why on the sign-up form? Why not in the profile..then you could send confirmation email? – paulj Mar 23 '21 at 20:03
37

Implement authentication and allow the proxying of the requests for authenticated users only.

Otherwise your service will become known with the time and other people or even companies can use it, and you will not be able to prevent it.

mentallurg
  • 10,256
  • 5
  • 28
  • 44
  • Thanks for this. The service can not be restricted to authenticated users only. It is a functionality that is needed when people sign up to our products. So we cant have it retricted to authenticated users only. – Johnny Donalistic Mar 22 '21 at 06:50
  • @JohnnyDonalistic so what is a problem? – Infra Mar 22 '21 at 07:49
  • 1
    @Infra Someone else can use the service whilst we pay for it. If there was a way to ensure that the API call is only made by our client, then the problem would be solved. – Johnny Donalistic Mar 22 '21 at 09:03
  • @JohnnyDonalistic can your website use techniques like csrf tokens to make direct use of the API more difficult? Every such measure can be circumvented, but even basic steps can cut down on invalid use a lot. Other approaches could include firewalls that try to detect bots. IP-based rate limits are a really good idea as well, though they can't defend against botnets. – amon Mar 22 '21 at 09:48
  • 24
    "It is a functionality that is needed when people sign up to our products." - So they are not your clients yet. Why do you want to restrict it to one part of not-clients and allow for another part of not-clients? – mentallurg Mar 22 '21 at 09:53
  • 11
  • 3
    Have people create an account as part of signing up for your products, then you could use authentication. Something similar to how you might need to provide information to get a quote or show the correct price based on where someone is located.. but make them create an account on your site before allowing them to get whatever info is needed from this API call – John-M Mar 22 '21 at 16:23
  • Would it be sufficient to limit guest users to only a few calls per day (i.e., as a sort of trial)? Possibly this limit could be by IP. – Brian Mar 22 '21 at 21:17
  • @JohnnyDonalistic the other way to do this would be letting the 'clients' who pay for your service provide API keys that're generated only for them and they have to provide it in the client side of the application. That's partial authentication. You can have an API-key-less one that is ratelimited to a few calls per day (ipinfo.io does this as do other platforms) so that they can't abuse the system. However, this requires you to use some type of limited auth by API key. – Thomas Ward Mar 23 '21 at 15:27
  • This is the correct answer, but don't authenticate the end user (who is trying to buy your products and doesn't exist yet) authenticate your javascript client. You are proxying the site requests from JS in your backend. Embed a public key in the client, challenge with a nonce (to prevent simple postman replay attacks) generated by the server, encrypted with priv key. Client decrypts and sends back along with encrypted, signed request. If all client side JS, attacker could still extract pub key and re-implement evil client, but you've raised the bar over a simple postman repost. – JesseM Mar 23 '21 at 22:03
  • @JesseM: As far as I understand, replay/repost is not the problem. The problem is, that other users who are not going to be clients want to use some service that costs the author money. For instance, there is a cool Captcha service or address validation service that other companies may want to reuse; and since there is no authentication it is not easy to prevent it. – mentallurg Mar 23 '21 at 22:37
26

As others have pointed out, you cannot completely prevent someone from calling your API if it's exposed to the internet unless you enable authentication.

If you want to restrict usage and make it inconvenient for abusers to call your API, you can issue a token on page load (CSRF token) and require that token to be present in the request to the API - that way the API will be callable from a browser that initiated a page load.

The API will not be directly callable without the token issued by your backend, but of course there are ways to automate web browsers via headless implementations to go around this.

You can implement server side quotas based on IP address - again there are ways to go around this.

A sufficiently motivated attacker will always find a way around it if there is significant value in your API.

However is it possible that you are overestimating the value? Maybe you can take an iterative approach: implement basic rate limiting, see how much abuse you get, improve your safeguards or disable when it generates costs that are beyond what you would be willing to accept.

Marek
  • 361
  • 2
  • 3
18

What is the best way for me to ensure that the API is only called from my JS client?

You don't.

Any sort of DRM that you put into your JS code -- like a secret API key that the JS client uses to prove to the server that it is an authentic client -- won't stay secret for long since javascript and the HTTP traffic that it generates is incredibly easy to inspect in a modern browser.

Instead, you need to authenticate and rate-limit the user. You've said that this API is called before users have accounts, but here are some ideas for rate-limiting:

  • reCAPTCHA so the user has to identify some bridges and stop signs each time they call this API,
  • make sure your front-end reverse proxy is setting the X-Forwarded-For or Forwarded header and then apply a rate-limit per client IP address. (though getting these headers right is surprisingly tricky, so I don't recommend an IP-based approach especially if your server is in a cloud environment)
  • If this is the kind of page where you have to fill out and submit a form, then you could even do something like give them a session cookie when they land at your site and only do the 3rd party API call if their session cookie is >= 10 seconds old (and reset that counter after doing the 3rd party API call).

The last advice I can give is don't make it useful; if you are directly proxying the 3rd party API (ie client gets to set whatever request content and gets to see the full response) then yeah, you've built a service where people can use the 3rd party API on your credit card. Don't do that; have the content at least partially filled in by your backend, or use the response in your backend rather than sending it to the JS client, or something.

Mike Ounsworth
  • 58,107
  • 21
  • 154
  • 209
12

Why call the third party API from your JS code at all? If this API has the potential to cost you a lot of money, move all of the code that interacts with that service to run on the server side. Don't leave anything on the client side that gives a malicious user enough information to make those API calls directly. Your JS code can make a request to a small web service running on your server, and that service will encapsulate everything related to the third-party API. Malicious customers could still spam your wrapper service with requests, but since you're now the gatekeeper between the user and the third-party service you have more leeway to implement rate limiting, add delays to requests, cache results, block IP addresses doing questionable things, or whatever else your service needs to do to deter abuse (based on the nature of the third-party service and how you're using it).

bta
  • 1,111
  • 6
  • 10
4

First and foremost, remember that your goal is not to totally eliminate use of your API other than via your client software, but to discourage unwanted use and limit any financial losses caused.

To determine whether measures taken to limit unwanted use are effective, make sure you have good visibility into how many API requests are being handled, how much those requests are costing you, where the requests are coming from, and so on. Periodically check the usage and see if it seems reasonable given the rate of new sign-ups. Rate-limit both the total volume of requests and volume per source IP, etc. Throttle users who are making too many requests.

I don't know what business you are in, but if you know that all your legitimate customers are in a certain country or region, you could use geolocation to block requests coming from other regions.

Distribute your client software with an access key which is different for each download of the software. (Generate an access key and embed it in the client installer on each download.) Track the usage of each access key. Expire access keys after a reasonable period of time or number of uses. (In case a legitimate customer gets stuck with an expired access key, your client software could pop up a message asking them to phone your company to have the key reactivated.) Or, if the key is only needed when the customer is signing up for your service, as you said, you could expire it immediately after they successfully sign up.

Alex D
  • 181
  • 3
3

Do it in 2 steps.

  1. Pre-register user without using your valuable service. Send user an Email to confirm the registration.

  2. When user opened your Email and launched confirmation process, you can use your valuable service (Captcha? Credit card validation? Address validation? Whatever.)

This has following advantages:

  • Bots will need to use real Emails. If you see too many registrations in a short time from the same domain, you can limit them or block the whole domain.
  • If some other company wants to use your service, their users will receive registration Emails from your company. The most companies will not want it and that's why they will not use your service.

Thus the number of non-clients who use your service will be very small. The most users will be your real clients.

I think this is the best you can get in your case.

mentallurg
  • 10,256
  • 5
  • 28
  • 44
0

There are the so called API management systems like 3scale, API Umbrella or whatever. You can setup throttling, authentication, firewall, etc.

This is more in line with @bta's answer as you are effectively doing it on your server side. Still this is an option to consider in case you do serious API integrations.

akostadinov
  • 575
  • 4
  • 8