简体   繁体   中英

Securing my API to only work with my front-end

I'm building a node/express backend. I want to create an API that only work with my reactjs frontend (private API).

Imagine if this is an e-commerce website, my users will browse products and will then choose what to buy and at the time of order they might or might not login.

What is the best practice to make sure my APIs will only work with my reactjs frontend?

What happens when users decide to login or if they remain as guests?

Apply CORS - server specifies domains allowed to request your API.

How does it work?

  1. Client sends special "preflight" request (of OPTIONS method) to server, asking whether domain request comes from is among allowed domains. It also asks whether request method is OKAY (you can allow GET, but deny POST, ...) .
  2. Server determines whether to allow, or deny request. It responds with "OK" response and set special headers that tell what domains/request methods are allowed.
  3. If client is allowed to query your API, it performs intended request, or bails out...

Clients that do respect CORS (browsers do) will be (or will not be if denied) able to connect. If client ignores CORS (REST clients, CLI tools, ...) it will be able to connect no matter what...

Still, require signed requests (authorisation)

This use case is an interesting one and I think a problem for many e-commerce sites. The product I am working on has actually had some conversations with companies trying to handle exactly this sort of thing in the mobile space. User logins can be used to tell you who is using the API, but if you don't want to force people to have a username/login you have to search for alternate solutions. What you seem to want is a way of identifying what software is attempting to use your API.

There are a couple of straightforward ways that are typically used to address this problem:

Embedded Secret

You can add a secret key to your app and require that any access to the API identifies itself using the key. People will tell you not to do this because it is really easy to extract the key. This is true, but with all security there is a cost/benefit analysis to be done to assess how much work you want to put in to protecting your API. The problem with javascript is that it is not that easy to obfuscate or hide secrets because all of the source code is right there.

If you were thinking about an environment where you had other options for your language choice then you can do more to obfuscate the secret in your app (like using the NDK in android for example). Javascript is difficult though.

The important thing to remember with an API key is that you shouldn't ever transmit it in the clear. It is really easy to steal it that way. Instead you would sign your API traffic using the key so that the server can verify that the request came from something that has the key and knows how to sign it.

Rate Limiting

Though not actually a solution to the problem, depending on what you are trying to achieve this is an option. If you are worried about large numbers of requests coming from other applications, you can rate limit to a level above what a genuine app would do and you could further block or rate limit by ip address if too many requests came in.

I took help from the above-mentioned solution by @ThePragmatist .

I have a few environment-based configs on my React website like backend API base URL (ex. staging-api.test.com, dev-api.test.com), the current domain name (ex. staging.test.com, dev.test.com), etc. so I used the variables to create a token to be sent on each public request (from a public request I mean the requests that don't need authorization). So the process I followed:

On Client side:

  • Create a string with user-agent/IP/something else from the header + request timestamp + _ + random 6 digit string
  • Create a JWT token using any environment config (I used the combination of few like backend_api + domain + another config ) as the secret key and the string generated in above step
  • Send the generated token in a custom header named token

On server-side to verify :

  • A middleware is used to authenticate any public API request which has the Redis implementation to stop the user to use the same token for a new request.
  • Verify the JWT token received in the header token . If JWT can verify, then process ahead otherwise return with 403
  • Check the timestamp received in the request. If the timestamp is of 2 mins or earlier, reject the request with 524 (or something else according to your need) otherwise move ahead
  • Check if the token has a random 6 digit string at the end. If not, reject the request as the format of the token was wrong
  • Check if a redis-key for the mentioned token header value exists. If it does, that means the same header was used in a request earlier, then simply reject the request with 403 otherwise move ahead
  • Save the token header in Redis with expire time of 2 mins + 10 seconds (took 10 seconds extra just for a buffer)

This way, all the public requests will send a token which is hard enough to guess by the spammer/hacker as we used multiple configs as private-key to sign the header. Also, they won't be able to use the same header in another request. Only the client app will be able to generate the token as we followed multiple things like a header, the timestamp, a random string in the end (just to create some confusion), etc.

I hope it solves someone's query.

EDIT :

The random 6 digit string can be verified too on the server-side if we use TOTP (Time-based OTP) with an expiry time of 2-4 mins. This will make the generated token stronger as we will be able to verify every possible part of the token.

So, this might be a slightly lengthy answer -- but you've posted a rather interesting and important question.

As someone who spends a majority of my time writing security libraries in Node and Python to handle this exact sort of thing, I figured I'd jump in here.

The protocol you want to use to protect your React app and backend API is the OAuth2 Password Grant flow. The way it works in theory is quite simple.

On your React app, you collect a user's username/password (this could also be an email/password if that's how you've structured your application).

You then send a POST request to your backend API that looks something like this:

POST api.myapp.com/oauth/token

grant_type=password&username=USERNAME&password=PASSWORD

Make sure you use the application/x-www-form-urlencoded content type when posting to your server.

Your server will then take this request, run it through an OAuth2 library, and generate two tokens: and Access and Refresh token.

Once you've got the tokens generated on your server side API, you'll then store those tokens in a cookie which will then be stored by the user's browser.

From this point on: everything should be automatic. When your React server makes API requests to your backend, the browser will identify the user via that cookie containing those two tokens automatically.

You'll need to use an OAuth2 library for your server-side, as this will handle things like:

  • Generating tokens.
  • Exchanging a Refresh token for a new Access token when it expires.
  • Identifying the user based on the tokens.
  • Revoking tokens if they are compromised, etc.

There's quite a lot more to it, but this is the basic, high level idea.

As you'll notice: there are no API keys involved here. When you're working with untrusted environments (eg: mobile apps, or client side javascript apps) it is completely unsafe to store permanent API tokens -- the reason is that they can be easily extracted from source code, or javascript.

Using the flow mentioned above instead is much safer, as you get a lot of protection:

  • No permanent credentials stored in an unsafe location.
  • Short-lived tokens are used as identifiers. These are rotated over time.
  • Tokens are stored in cookies that are NOT accessible to Javascript. This means less risk of web exposure.
  • Password are only exchanged once, for the duration of a session -- this means less sensitive information is going over wire less frequently =)

Anyhow: hope this helps!

And, if you're looking for some tools, any oauth library (server-side) should help you with this stuff. If you're looking for a service that can do this for you, you might want to check out the product I work on ( Stormpath ). It's a paid service, but handles a lot of this complexity on your behalf.

As of today, any user can see what is being passed to your backend by inspecting the network tabs in your browser console. The only way to ensure your api is secured is through user's authentication using JWT or the likes. If your app is opened to guest users, then cors cannot really help because all a user has to do is make requests identical to what they've seen in the browser's console to your apis through curl or postman.

I know this is late. But just putting my thoughts here.

Use of reCAPTCHA V3 might help in your case as the reCAPTCHA tokens can be created only in browser(depends on your configuration) and bound to a domains mentioned while creating the keys. When ever a request is made to backend, send the short-lived token created on the frontend. Backend can cross check the token and verify its validity. Below code provides token using Google reCAPTCHA

<script src="https://www.google.com/recaptcha/enterprise.js?render=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"></script>
<script>
grecaptcha.enterprise.ready(function() {
    grecaptcha.enterprise.execute('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', {action: 'login'}).then(function(token) {
       ...
    });
});
</script>

So one of way of doing While sending any AJAX requests to your server, send a header which contains somekey like apiKey : "some key". In server whenever you receive it and authenticate the domain on which your react app is getting served. If not you don't need to respond. Something like

app.get('/route', function(req, res) {
  if(req.headers.apiKey == "some key) // respond with success
  // respond with 401 Forbidden
});

So any request which hits the server should have apiKey in order to make i work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM