Secure API calls with AJAX and PHP for a third-party API

I want GET, POST and PUT to call a third-party API and display the client-side response via AJAX. API calls require a token, but I need to keep this token secret / not in client-side JS code.

I saw several suggestions like this to have the server code in the middle that will be requested by AJAX and handle the actual API call. I normally work directly with the API from AJAX, but I'm not sure how to work with the two-step process to hide the token from users. My Googling did not find any pointers on the best method to achieve this.

In my case, the server in the middle will run PHP, so I assume cURL / Guzzle is a simple way to make API calls with a token. API responses will be JSON.

Can someone please give me an example of how this will be achieved using jQuery.ajax (), for PHP, a third-party API?

Alternatively, if there are any quality resources that describe this method in detail, I would appreciate the link. Similarly, if this is a terrible use method, it would be great to know why.

Edit
It is probably worth noting that I want as much flexibility as possible when deploying this; it will be used on several sites with unique configurations, so ideally this will be implemented without changing the configuration of the server or hosting account.

+7
javascript jquery ajax php php-curl
source share
6 answers

This bit is hard with no sample code. But, as I understand it, you can follow this,

Ajax call

$.ajax({ type: "POST", data: {YOU DATA}, url: "yourUrl/anyFile.php", success: function(data){ // do what you need to } }); 

In php

Gather your published data and process the API, something like this

 $data = $_POST['data']; // lets say your data something like this $data =array("line1" => "line1", "line2"=>"line1", "line3" =>"line1"); $api = new Api(); $api->PostMyData($data ); 

API class example

 class Api { const apiUrl = "https://YourURL/ "; const targetEndPoint = self::apiUrl. "someOtherPartOFurl/"; const key = "someKey819f053bb08b795343e0b2ebc75fb66f"; const secret ="someSecretef8725578667351c9048162810c65d17"; private $autho=""; public function PostMyData($data){ $createOrder = $this->callApi("POST", self::targetEndPoint, $data, true); return $createOrder; } private function callApi($method, $url, $data=null, $authoRequire = false){ $curl = curl_init(); switch ($method) { case "POST": curl_setopt($curl, CURLOPT_POST, 1); if ($data) curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); break; case "PUT": curl_setopt($curl, CURLOPT_PUT, 1); break; default: if ($data) $url = sprintf("%s?%s", $url, http_build_query($data)); } if($authoRequire){ $this->autho = self::key.":".self::secret; // Optional Authentication: curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($curl, CURLOPT_USERPWD, $this->autho); } curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($curl); curl_close($curl); return $result; } } 
+4
source share

Since all you want is to add a token to the http headers , which I assume is Authorization , a simple way would be to implement a proxy server that makes calls to your api endpoint after they are added. A sample file for nginx will be

 location /apiProxy { proxy_pass http://www.apiendPoint.com/; proxy_set_header Authorization <secret token>; } 

This is a much more reasonable approach than writing a program, and you get 4 lines of code. Be sure to change your parameters and add other parameters as needed by the api client you are using. The only difference in the javascript side would be to use the location url , rather than the one provided by the service, which acts as a proxy.

Edit

The configuration for apache will be

 NameVirtualHost * <VirtualHost *> <LocationMatch "/apiProxy"> ProxyPass http://www.apiendPoint.com/ ProxyPassReverse http://www.apiendPoint.com/ Header add Authorization "<secret token>" RequestHeader set Authorization "<secret token>" </LocationMatch> </VirtualHost> 
+8
source share

From your requirements, it looks like an “average server code” relay (proxy) script - the best option.

PHP example here . However, for handling CURL errors, a new “object” is returned containing ['status'] ('OK' or CURL failure information) and ['msg'] containing the actual response from the API provider. In your JS, the original “now” API object requires one level down to “msg”.

Basic relays / proxies can be bypassed

If you use a relay script, then someone looking for an API key will probably try elsewhere. But; a pirate can simply replace his call to an API provider using your API key with a call to your script (and your API key will still be used).

Running your AJAX / relay script by search engines

Google bots (others?) Do AJAX. I guess (relaying or not) if your AJAX does not need user input, then bot visits will result in the use of an API key. Bots are "improving." In the future (now?) They can emulate user input, for example. if you select a city from the drop-down list in the API request, Google can change the parameters of the drop-down list.

If you are worried, you can enable validation of your relay script for example.

  $bots = array('bot','slurp','crawl','spider','curl','facebook','fetch','mediapartners','scan','google'); // add your own foreach ($bots as $bot) : if (strpos( strtolower($_SERVER['HTTP_USER_AGENT']), $bot) !== FALSE): // its a BOT // exit error msg or default content for search indexing (in a format expected by your JS) exit (json_encode(array('status'=>"bot"))); endif; endforeach; 

Relay script and additional code to solve the above problems

Do not overdo it with pirate protection; relays should be fast and invisible to visitors. Possible solutions (without experts and rusty sessions):

1: Solution for PHP Sessions

Checks to see if someone who visited your AJAX page in the last 15 minutes has called, provided a valid token and has the same User Agent and IP address.

Ajax pages add the following snippets to your PHP and JS:

  ini_set('session.cookie_httponly', 1 ); session_start(); // if expired or a "new" visitor if (empty($_SESSION['expire']) || $_SESSION['expire'] < time()) $_SESSION['token'] = md5('xyz' . uniqid(microtime())); // create token (fast/sufficient) $_SESSION['expire'] = time() + 900; // make session valid for next 15 mins $_SESSION['visitid'] = $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']; ... // remove API key from your AJAX and add token value to JS eg $.ajax({type:"POST", url:"/path/relay.php",data: yourQueryParams + "&token=<?php echo $_SESSION['token']; ?>", success: function(data){doResult(data);} }); 

Relay / proxy script (session version):

Use the existing sample relay script before adding the CURL block:

  session_start(); // CHECK REQUEST IS FROM YOU AJAX PAGE if (empty($_SESSION['token']) || $_SESSION['token'] != $_POST['token'] || $_SESSION['expire'] < time() || $_SESSION['visitid'] != $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] ) { session_destroy(); // (invalid) clear session variables, you could also kill session/cookie exit (json_encode(array('status'=>'blocked'))); // exit an object that can be understood by your JS } 

Assumes default session settings. Required cookies and page / relay in one domain (possibly working). Sessions can affect performance. If the site already uses sessions, the code should consider this.

2: Carefree / No Cookie

Uses a token associated with a specific IP address and a User Agent valid for a maximum of 2 hours.

Functions used by both the page and the relay , for example. "Site-functions.inc":

 <?php function getToken($thisHour = TRUE) { // provides token to insert on page or to compare with the one from page if ($thisHour) $theHour = date("jH"); else $theHour = date("jH", time() -3600); // token for current or previous hour return hash('sha256', 'salt' . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . $theHour); } function isValidToken($token) { // is token valid for current or previous hour return (getToken() == $token || getToken(FALSE) == $token); } ?> 

Relay script Use an existing example before adding a CURL block:

 // assign post variable 'token' to $token include '/pathTo/' . 'site-functions.inc'; $result = array('status'=>'timed out (try reloading) or invalid request'); if ( ! isValidToken($token)) exit(json_encode(array('msg'=>'invalid/timeout'))); // in format for handling by your JS 

Pages requiring an API (or your javascript-enabled file):

 <?php include '/pathTo/' . 'site-functions.inc'; ?> ... // example Javascript with PHP insertion of token value var dataString = existingDataString + "&token=" + "<?php echo getToken(); ?>" jQuery.ajax({type:"POST", url:"/whatever/myrelay.php",data: dataString, success: function(data){myOutput(data);} }); 

Note. User agent is faked. IP (REMOTE_ADDR) cannot be faked, but setting up on fewer sites can cause problems, for example. if you are behind NGINX, you may find that REMOTE_ADDR always contains the NGINX IP address.

If you use a typical third-party API that will provide confidential NON information until you reach the usage limit for your API key, then (I think) the above solutions should be enough.

+3
source share

As people pointed out, you need a proxy method on your server to hide the API key.

To avoid the incorrect use of your method on the server, protect the call with a one-time token (for example, you usually use for forms) - generated from your server (not in javascript ..).

I am not a fan of the pasted code encoded above that checks for known HTTP user agents ... or site tokens ... this is not safe.

+2
source share

If you are using cUrl, which must be protected, this is your server. The way I personally use it is Google reCaptcha, which is surely made to organize problems like yours. This explains the client-server integration in great detail. https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 Thus, you do not need to change anything in virtualhost files and any apache configurations.

+1
source share

I would publish the published solutiuon @MMRahman if you want to add a level of security between your backend and your interface, what can you do when the user logs in, generates a unique identifier, stores it in a server session and in a cookie or local / session browser server Thus, when you call your backend using ajax, you can get the value from where you store it in the browser and check if the values ​​match, if so, you call the external api and return the values, if not just a game Generate a request.

So, to summarize: Logging in to the system → generate a unique identifier → store it in a server session and a browser session → make a call from ajax, passing the value from the browser session as a parameter-> check if it matches the saved value of the server session → if so, call the external api using the token stored in your file / db / file / no matter what you want

0
source share

All Articles