Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Secure API calls with AJAX and PHP to 3rd party API

I want to make GET, POST & PUT calls to a 3rd party API and display the response on the client side via AJAX. The API calls require a token, but I need to keep that token secret / not in the client-side JS code.

I've seen a few suggestions like this one to have server-side code in the middle that would be queried by the AJAX, and would handle the actual API call. I'm OK working directly with the API from AJAX, but I'm unsure of how to work with a two-step process in order to hide the token from users. My Googling hasn't turned up any pointers on a best-practice method of achieving this.

In my case the server in the middle would be running PHP, so I assume cURL / Guzzle is the straightforward option to make the API calls with the token. The API responses will be JSON.

Can anyone please give me a rough example of how this would be achieved using jQuery.ajax(), to PHP, to the 3rd party API?

Alternatively if there are any quality resources that cover this method in detail, I'd appreciate a link. Equally, if this is a terrible method to use it'd be great to know why.

Edit
Probably worth noting that I want as much flexibility in deploying this as possible; it would be used on multiple sites with unique configurations, so ideally this would be implemented without altering server or hosting account configuration.

like image 941
t-jam Avatar asked Feb 23 '18 12:02

t-jam


People also ask

Can you use AJAX with PHP?

Start Using AJAX Today In our PHP tutorial, we will demonstrate how AJAX can update parts of a web page, without reloading the whole page. The server script will be written in PHP. If you want to learn more about AJAX, visit our AJAX tutorial.

Can we call API using AJAX?

AJAX (Asynchronous JavaScript and XML) is a set of tools used to make calls to the server to fetch some data. In this article, we will see how to implement a simple API call using AJAX. Prerequisites: Basic knowledge of AJAX and its function. You can learn all the basics from here.

Is PHP necessary for AJAX?

AJAX and PHP are often conflated since they are usually used in tandem, but it is essential to understand that you can use AJAX without PHP and vice-versa. So let's take a closer look at both of these technologies.

Is AJAX RESTful API?

AJAX is a set of (typically) client-sided web development techniques, while REST is an architecture style for sending and handling HTTP requests. So you can use AJAX to send RESTful requests. A REST API is typically not implemented using AJAX, but can be accessed by an AJAX client.


4 Answers

It is bit hard without sample code. But As per I understood 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

Collect your posted data and handle 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 );

Example API Class

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;

 }
}
like image 58
MMRahman Avatar answered Oct 17 '22 15:10

MMRahman


I would use the solutiuon @MMRahman published, if you want to add a security layer between your backend and your frontend what you could do it is when the user make login generate a unique ID, store it in the server session and in a cookie or local/session store of the browser, this way when you call your backend with ajax you can get the value from the place where you stored in in the browser, and check if the values are the same, if yes you call the external api and return the values if not just ignore the request.

So summaring: User login -> generate unique ID -> store it in server session and browser session -> make call from ajax passing as parameter the value from browser session-> check if it matchs with server session stored value -> if yes call external api using the token stored in your backend / db / file / whatever you want

like image 21
Emilio Camacho Avatar answered Oct 13 '22 20:10

Emilio Camacho


Because all you want is to add token to http headers, which i am assuming is Authorization a simple way would be to implement a proxy server that makes calls to your api endpoint after adding up those. A sample file for nginx would be

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

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

Edit

The configuration for apache would 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>
like image 8
georoot Avatar answered Oct 17 '22 15:10

georoot


From your requirements it looks like "server-side code in the middle" relay(proxy) script is the best option.

PHP example here. N.B. to handle CURL errors it returns a new "object" comprising ['status'] ('OK' or info on CURL failure) and ['msg'] containing the actual response from the API provider. In your JS the original API "object" would now require extracting one level down under 'msg'.

Basic Relays/Proxies can be circumvented

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

Running of your AJAX/relay script by search engine bots

Google bots (others?) execute AJAX. I assume (relay or not) if your AJAX does not need user input then bot visits will result in API key usage. Bots are "improving". In future (now?) they might emulate user input e.g. if selecting a city from a dropdown results in API request then Google might cycle thro dropdown options.

If of concern you could include a check in your relay script e.g.

  $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 cater for above issues

Do not overdo pirate protection; relays should be fast and delay unnoticeable by visitors. Possible solutions (no expert and rusty with sessions):

1: PHP sessions solution

Checks whether relay is called by someone who visited your AJAX page in last 15 mins, has provided a valid token, and has the same User Agent and IP Address.

  Your Ajax Pages add the following snippets to your PHP & 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 e.g.
  $.ajax({type:"POST", url:"/path/relay.php",data: yourQueryParams + "&token=<?php echo $_SESSION['token']; ?>", success: function(data){doResult(data);} });

  The relay/proxy Script (session version):

  Use an existing example relay script and before the CURL block add:

  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's variables, you could also kill session/cookie
    exit (json_encode(array('status'=>'blocked'))); // exit an object that can be understood by your JS
  }

  Assumes standard session ini settings. Cookies required and page/relay on same domain (workround possible). Sessions might impact performance. If site already uses Sessions, code will need to take this into account.

2: Sessionless/Cookieless option

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

  Functions used by both page and relay e.g. "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 and before the CURL block add:

// 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 needing the API (or your javascript include 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 spoofable. IP (REMOTE_ADDR) "cannot" be faked but setup on a minority of sites can cause issues e.g. if you are behind NGINX you may find REMOTE_ADDR always contains the NGINX server IP.

If you are using a typical 3rd party API that will provide NON sensitive information until you reach the usage cap for your API Key then (I think) above solutions should be sufficient.

like image 5
scytale Avatar answered Oct 17 '22 16:10

scytale