4

I am developing an application where I provide a JavaScript to my clients (stored on my CDN), and they can load it to their web pages via a script tag. This JavaScript does some checks on the DOM and sends the information to a REST API I provide (no user interaction needed, this is a background task). The clients who introduced the JavasScript into their web pages can login on my website and check the statistics I have collected via the JavaScript file.

I do not have a clear idea on how to make it secure, or even how much secure I can make it, as the only parts involved here are the script I provide and my REST API. No interaction between the back-end of my client and my REST API is required (at the moment, to be reviewed if no security at all can be reached). I know I can not put any token or credentials on the JavaScript file obviously. The only idea I have is to check the "origin" of the request and validate it agains a whitelist, but I am not sure if even the origin can be tricked (I think it actually can be tricked), so having to maintain a whitelist for not reaching a good level of security seems a waste of time.

Does anybody know how other services (like Google Tag Manager or any other SEO utility) works to prevent intentioned fraud? How I make sure that requests pushed to my API are legitime?

Edit: My current context is that only the JavaScript and the REST API are involved. And that seems impossible to make it secure. Which steps should I follow in order to make it secure? Involving the back-end of my client and using a public/private key pair looks good, but even in this way, I would need users to be logged in on my client's web site, in order to make the requests to their server secured and not public, right?

Thanks!

2
  • What are you trying to secure and what are you trying to secure it from? May 18, 2018 at 18:15
  • Thanks for the comment. I have edited the question to make it more clear: The exact question is, how I make sure that requests pushed to my API are legitime from my clients, and not anybody else.
    – Jordi
    May 18, 2018 at 18:24

2 Answers 2

2

You can have an authentication backend, and users have to authenticate first, get a token, and use the token on subsequent requests.

Hopefully only your users will have credentials to your service (the exception being users with weak, leaked passwords), and the only ones able to interact with the API. But you should employ rate-limiting on the backend so bot owners will not brute-force the authentication backend.

1
  • Users do not authenticate first. This is more like when you add a library to check SEO things. Thanks for the answer.
    – Jordi
    May 22, 2018 at 12:24
2

You certainly can't trust the Origin header generally. You can trust the origin header when it is a request from a browser, but if malicious attackers are using your endpoint directly via a client they control, then the origin header is meaningless. So whether or not it helps depends on the circumstances.

The simplest way is the same way everyone secures API access - grant API tokens to users and have your clients register their API token with your javascript on page load. You might often see things like this:

<script type="text/javascript" src="//awesome.service.com/js">
<script type="text/javascript">
    AwesomeService::SetPublicToken('THISISMYAUTHTOKENASSIGNEDBYYOURSYSTEM');
</script>

Now obviously that authentication token is public - anyone viewing your client's page can see their token and steal it. Hence the emphasis on Public token. Different services address this issue in different ways, depending on what you are trying to secure. The most common solution is to have different private and public keys with different security rules or options associated with them. Let's consider two examples: a mapping service (i.e. google maps) and a credit card processor.

Mapping Service

Let's imagine a javascript file that draws maps in a browser. You want your clients authenticated so that you can make sure everyone is paying for this service. This can be effectively protected by simple public authentication keys and allowing the client to configure their list of allowed hosts. Since this is really only useful if the API calls are used to display info in a browser, it is a fair assumption that your API will be called from a browser, and therefore using an Origin header check against your list of white-listed domain names (provided by the client) makes sure it is being used as intended. This makes sure that the public key isn't just copied to a new website and used to get free service by someone else.

Regarding usage outside of a browser, there is really no motivation for someone to do that other than spite. Sure, someone could copy your public token and generate a script that makes API calls using it and pretending to be from a white-listed domain (because using a non-browser HTTP client lets you easily spoof the Origin header), but why? Since this mapping service just returns maps to display in the browser, there is no gain. The only reason to really do it is if you really don't like the business you stole the public auth key from, and want to run up a large bill on their service. Probably not the most common attack vector.

So in a case like this public authentication tokens and an origin check are more than sufficient for the use case.

Credit card processor

Imagine you have a service where you have truly sensitive information - credit cards! You want your clients to be able to process credit card transactions using your handy javascript file so that it can be done in a PCI compliant way (i.e. Stripe) without leaving the checkout page. You need this to happen in a browser, but Public Tokens plus Origin checks are no longer enough. With direct API access (via Origin spoofing), a malicious user may use the API to refund their own credit card, potentially stealing money from the client's business.

So what do you do? Separate out secure actions with a private authorization token, and only allow the public authentication token to request actions. Those actions must then be authorized via a separate API call from the client's server itself using a private key not shared with anyone. For Stripe it looks like this:

  1. Javascript library in end-user's browser collects credit card information
  2. Javascript library in end-user's browser sends CC info to Stripe using public key
  3. Stripe sends back a single-use "transaction" token that is useless on its own
  4. Javascript in end-user's browser sends authorization token to (e.g.) e-commerce site that is using stripe service
  5. E-commerce site sends additional API call directly to stripe with single use "transaction" token using a private authorization token, telling Stripe what to actually do with the corresponding credit card (aka bill it, refund it, etc...).

This is obviously a more complicated flow, but it is necessary to protect sensitive API calls because nothing in the browser is private and there is no way to guarantee that you are actually talking to a browser if you are looking from the perspective of an API service.

I suppose I'm answering by example, but it's hard to give a comprehensive answer because so much depends on the circumstances. The details of your service decide what steps you actually take, but the short of it is that truly sensitive services can't really be protected if only client-side javascript is involved. Again, the reason is because everything on the client-side is effectively public information, and all aspects of an HTTP request can be spoofed.

5
  • Thank you very much for your answer. The "Credit card processor" example is the one that fits more in my case. I have a question on step 4) When you say "Javascript in end-user's browser sends authorization token to (e.g.) e-commerce site that is using stripe service", it is a separate JavaScript different from the Library provided by Stripe, right? Meaning that some coding by the e-commerce site is required? Another question: Somebody could have requested the single-use transaction token by itself and then use it to send it to e-commerce API directly?
    – Jordi
    May 22, 2018 at 12:21
  • Yes, that is separate javascript different from the library provided by Stripe. The e-commerce site is responsible for providing that as part of their Stripe integration. It is usually very simple, and Stripe (at least last time I did this as an e-commerce provider) had lots of documentation to make it easy for people with limited technical experience. However, some amount of javascript coding was required for e-commerce systems to use their javascript, as a result of the security needs outlined above. May 22, 2018 at 12:31
  • @Jordi yes, there is theoretically nothing preventing someone from making the API call themselves with the public API and sending it up to the e-commerce provider. Some security on the provider side can make sure that credit cards are only billed in a proper context (aka an eocmmerce with an API endpoint that takes a transaction token, dollar amount, and issues a refund without question would be a terrible idea). Our billing was connected to orders so if someone just hit our endpoint with a transaction token it wouldn't do anything unless it was connected to an actual order ready to bill. May 22, 2018 at 12:35
  • @Jordi Feel free to edit your question with more details about your particular use case so perhaps I could provide a more relevant example. May 22, 2018 at 12:35
  • Probably in case of Stripe, the e-commerce site has information about the user (because is probably logged in, in order to proceed with the buy) so in point 4) it can be sure that the request from the client JavaScript to e-commerce back-end is secured. In my case, the user who is visiting the website is not a registered user so I think I would break here the "secured" chain. Correct me if I'm wrong. For what you told, I believe that, involving just the JS and my API, I can not make it secure, probably? I have updated the question to make it more clear, hopefully @Conor, thanks for your time.
    – Jordi
    May 22, 2018 at 18:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .