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

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.

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;
}
}

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>

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.

As people pointed out, you want a proxy method on your server to hide the API-key.
To avoid misuse of your method on the server, protect the call with an one time token (like you usually use for forms) - generated from your server (not in javascript..).
I am not a fan of the coded pasted above which checks for known http-user agents... or site tokens ... this is not secure.

If you use cUrl that you must to protect is your server. The way I personally use is the Google reCaptcha that is sure made for arranging problems like yours. Very well explained the integration in client and server sides step by step here.
https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024
With this way you don't need to change anything in your virtualhost files and any apache configurations.

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

Related

Different ways to use local variables from HTML / Javascript in PHP

Recently I have been doing a lot of work in PHP and I have become familiar with how it works. I stand by what I have said before; That every problem has an endless amount of solutions. So that is what I am after, solutions that solve the same problem.
In this case, I want variables/references to values from localstorage:
localStorage.setItem("user", "bananaflakes55");
localStorage.getItem("user");
and directly include them in PHP files. Now I have found out that using echo have a variety of uses, for example:
echo '<script type="text/javascript"> window.location.replace("' . $refclinklogin . '"); </script>';
Granted that the value there are on serverside -> client side. In this case I want similar solutions that necessarily wont require me to create a GET or POST, with HTML elements like forms, that connect these.
To sum up, I want solutions that can bring values from local and session storage, to PHP. Bring forth some funky ideas, if possible. From what I have read it is a tricky one.
Even if i understand what you want, process sould be running from PHP to client rather than the reverse.
With this in mind, a light solution can be something like that :
window.addEventListener("load", function() {
let request = new XMLHttpRequest();
request.open("POST", 'localStorageToSession.php', true);
request.onload = function () {
let saveResponse = request.responseText;
// if you want a callback or use some script return
}
let data = "jsonLocalStorage=" + JSON.stringify(window.localStorage);
request.send(data);
}) ;
Once the page is loaded, send all localStorage parsed in json to a PHP treatment (here called localStorageToSession.php).
So you can convert localstorage as $_SESSION. Something like that :
$_SESSION['jsLocalStorage'] = json_decode($_POST['jsonLocalStorage'], true) ;
Then you can use $_SESSION['jsLocalStorage'] in your backend treatments. Don't forget to add session_start() on all your files.
You can save the xml request in a function and call once localStorage is updated).
Even if that solution works, i don't recommand it if you have to deal with safety informations like passwords or user special access.

How to store $_SESSION variables in Javascript for XMLHttpRequest purposes?

I asked this question a while ago and I deleted that question because I though I found the appropriate method to solve my issue.
I'm making a website which works with lots of XMLHttpRequest. I need to send the user_id of the logged in client to a php file located elsewhere which will send sql statements to the database, and return the information according to the logged in client. I do not have any knowledge of security. I only have little knowledge of sql and php. So I want to make my website secure, so I need some advice on security and the appropriate way to send user_id via to the php file.
Or does it even matter if the user_id is being shown on the client side. And also I'm storing the unique user_id of the client in the $_SESSION only, nothing else.
I have made my login/sign-up system entirely using this source = https://www.youtube.com/watch?v=xb8aad4MRx8&t=674s
index.php:
<?php
session_start();
?>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEST</title>
</head>
<body>
<?php
if (isset($_SESSION['user_id'])) {
echo ('<button>CLICK TO GET INFO IN CONSOLE</button>');
} else {
header ("Location: login.php");
exit();
}
?>
<script>
var user_id = <?php echo($_SESSION['user_id']);?>;
document.querySelector("button").onclick = function() {
var xhr = new XMLHttpRequest();
var url = "http://URL_OF_A_PHP_FILE_LOCATED_IN_A_DIFFERENT_LOCATION";
xhr.open("GET", url + "?user_id=" + user_id + "&var_1=val_1&var_2=val_2&var_3=val_3");
xhr.onload = function() {
console.log(JSON.parse(xhr.responseText));
}
xhr.send(null);
}
</script>
</body>
</html>
You don't have to use javascript to share information between PHP files (even if on separate servers).
$_SESSION is used explicitly to keep data between pages and you should handle it within them as such.
If you have to send user_id to a file on a different server, it's advised to do it inside of your PHP files and not Javascript (which is clientside and thus very prone to be abused).
You could simply use curl to perform a HTTP POST request.
First off, I will assume that you are rather new to PHP, and that is for a very specific reason: $_SESSION is meant to be a server-side storage, and should not go to the client, at any time. Because you may store sensible data in a session, which you do not want to transmit over the network, especially since you dont want any unprevileged person to sniff the data and eventually leave yourself wide open for an RCE (Remote Code Execution) or SQL Injection attack.
When you look at individual values however, that is a little bit of a different story. A User ID can be used - but what if I ran a generator attack against this page with a range of 0 to 100 with each and every of those numbers being used as an ID? You need to consider this at any given time. It is very important.
Now - to actually answer your question.
First, there are two approaches you can take: Actually and really turning over the User ID, as long as you are sure that this ID can not be used for malicious attempts OR you can use Cookies with encrypted values - or, encrypt the cookies in general.
For option one, you'd do this:
var user_id = <?=json_encode($_SESSION["user_id"])?>;
This will encode your user_id into JSON (JavaScript Object Notation) - which essentially is valid JavaScript, per-se. Therefore, if $_SESSION["user_id"] happens to be an integer like 1, the result would look like this:
var user_id = 1;
The second option of using encrypted cookies is a little bit, if not much trickier but also much more secure. Consider this:
<?php
$salt = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$cipher = "AES-128-CTR";
function encryptValue($value) {
$secret = "..."; // See below
$encryptedValue = openssl_encrypt($value, $cipher, $secret, 0, $salt)
. "::" . bin2hex($salt);
return $encryptedValue;
}
function decryptValue($value) {
list($encryptedValue, $salt) = explode("::", $value);
return openssl_decrypt($encryptedValue, $cipher, $secret, 0, hex2bin($salt));
}
?>
(Resource: https://www.the-art-of-web.com/php/two-way-encryption/ )
Now, this is a very, very simplified encrypt/decrypt mechanism based on the OpenSSL extension and it will essentially encrypt values for you - and decrypt them. The important part is the $secret. This is your encryption "password" that is being used to obfuscate what the user gets to see. Because basically, avoiding attacks starts by making it impossible to decipher what the values being sent to the server actually mean. In a production environment, at least. In development, you may actually leave this as clear-text - but that is a different topic.
So, now with those two functions available, you can do this:
<?php
$value = null;
if(array_key_exists("user_id", $_COOKIE)) {
// The user has previously been assigned a `user_id`.
$value = decrypt($_COOKIE["user_id"]);
} else {
// The user is possibly visiting for the first time, so they have no ID.
$value = get_user_id_from_somewhere();
}
?>
Now you will have a $value containing either a fresh ID or one that had been sent beforehand.
But now you need to send this value to the client so it can be used in a XHR request. Well, you will need to set the cookie first. Once done so, you can take the example from the first option and do this:
var token = <?=json_encode(encrypt($_COOKIE["user_id"]))?>;
And with "something" I really mean it - the way you get to your encrypted token is up to you :)
Next, you may want to use it to send a XHR request, so you will need to encode it for a URL. You can even use this right away and save the token variable alltogether with something like:
var url = "http://example.com/other.php?token=<?=urlencode(encrypt($_COOKIE["user_id"]))?>";
To access that URL parameter, use $_GET["token"] in other.php and decrypt it with the function from above (or with any other you come up with, really).
So which option should you choose in which case?
- If you transfer data to your client which you are very sure can not be exploited and is safe for them to know about, use json_encode and the raw variable from $_SESSION. Keep in mind, that session variables are meant to be private to the server. In fact - PHP will actually send a PHPSESSION cookie to the client, which uses such a hashed, almost encrypted-looking string, in order to identify the visitor on subsequent requests.
- If you transfer data which you definitively do not want the client to see or even know about, encrypt it. The fewer the client knows about your business logic (contents of your scripts), the lower are the chances for an attack.
Also: Never trust a user, ever. This is very much an unwritten and yet golden rule. If you are not sure, then go with the most secure method you have at hand. Only if you are very, very sure, send values in a raw format to them - which they will be able to read in all their plain glory.
A little side-note to json_encode: You can also encode an entire array as well: json_encode(["foo"=>"baz"]) will become: {"foo":"baz"}, which is valid JSON, and JavaScript too.
Thus, one more side-note: If you haven't already, use PHP's official manual and start embracing Composer. You can find many, many useful tools there which can make your live as a PHP dev much easier.
I know this is a long answer, but I only mean well =) Good luck with your app!

I'm putting my entire $_SESSION variable into a json object on page load. While this works for me, is this a good practice?

I've been doing something this at the bottom of all my views:
<script type='text/javascript'>
$.post('php/ajax.php', {type:'session'}).done(function(data){
var session = JSON.parse(data);
$(document).ready(function(){
$.getScript('resources/redactor/redactor.js');
$.getScript('javascript/year_long_calendar.js');
$.getScript('javascript/edit_lesson_modal.js');
});
});
</script>
This works really well for me. All my scripts get loaded inside of a single docReady, and all my ajax requires a token that gets generated upon login and stored in $_SESSION. This stops people from hitting my ajax logic using fake headers. By doing this, my ajax calls look something like:
$.post(url:'ajax.php', {token:session.token, id:id}).done(function(data){ ... });
I can also access other session variables
var user_id = session.user_id;
Since I've been doing this from the start of the project, I intentionally keep any sensitive information like passwords out of the session variable. What are your thoughts on this? Does any of this strike you as insecure, or terribly inefficient? I realize $.getScript is often used as a lazy way to load libraries, but I think I've found a pretty valid use for it.
None of the data in $_SESSION is sensitive except the token, and you have to be logged in to get one. Unless someone malicious hops on a machine while the real user is away and knows exactly where my ajax logic is, how it works, how I store my session, and fakes a quick header on PostMan to delete all my tables, I don't see it being an issue.
EDIT:
#AnotherGuy helped me realize a much better solution. My ajax.php file now looks like this:
<?php session_start();
include('connect.php');
include('functions.php');
// check to see if http request is ajax (easy to fake but hey might as well)
if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
// when the user logs in, a random number is generated and saved to $_SESSION['token'].
// this block is used to pass the token to a javascript variable securely
if($_POST['type'] == 'session'){
$session = [
'token'=>$_SESSION['token'],
'user_id'=>$_SESSION['user_id']
];
echo json_encode($session);
}
// all post requests must pass the correct token variable to step into this block and access the ajax logic
if(isset($_POST['token']) && $_POST['token'] == $_SESSION['token']){
if($_POST['type'] == 'get'){
$where = null;
if(isset($_POST['where'])){
$where = json_decode($_POST['where']);
}
$order_by = null;
if(isset($_POST['order_by'])){
$order_by = json_decode($_POST['order_by']);
}
echo json_encode(get($_POST['db'], $_POST['table'], $where, $order_by)->fetchAll());
}
if($_POST['type'] == 'put'){
$set = json_decode($_POST['set']);
echo put($_POST['db'], $_POST['table'], $set);
}
if($_POST['type'] == 'update'){
$set = json_decode($_POST['set']);
$where = json_decode($_POST['where']);
update($_POST['db'], $_POST['table'], $set, $where);
}
if($_POST['type'] == 'delete'){
$where = json_decode($_POST['where']);
delete($_POST['db'], $_POST['from'], $where);
}
From how you describe you are using the session I cannot see any harm in it, but I still think it is dangerous. Imagine you in the future work on another project and then come back to this. Will you still remember not to store any sensitive information inside the session? As a basic rule of thumb is to never store sensitive information in the session unless it is the only solution, which it rarely is. But sometimes mistakes are made and they can hurt you!
I would change this to something that looks/works in the same way, but offers you more decoupling from the session. If you are fetching the entire session you are bound to retrieve some information which would never be used or should never be available to client side (through Javascript). I would create a single page that you request which can only provide the necessary information. That way you can also ensure only required information is exposed to the client side.
So instead of requesting a generic ajax.php file, I would create a page called (or something like it) userInfo.php. That way you can also eliminate the type variable you send along with it.
Hope this can help you, happy coding!
You could store that session data in browser with sesssionStorage in a serialized JSON string and manipulate it from there. Many recommend this approach over using cookies W3Schools
Cheers.

Capturing client request headers (complete) along with IP address information?

I want to write client request to a file with two things/information
headers
ip address
The code I'm testing on free hosting service.Code is
<?
$headers= apache_request_headers();
foreach ($headers as $header => value)
{
echo "4header: $ value </br >\n";
}
?>
Above, work for headers; but I'm confused what does apache_request_headers() means its given as example in offical php website. Does it work for apache only?
If, the request header doesn't contain IP address I want to capture it as under
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
Merging the two, I want to write in single file and append it with time information for every request?. If time info is included as part of request...then its not required for new code.
Also, please suggest what the best way to capture such request, be it in separate php which is reading all the request to particular page or directory ; or put it inside the i.e index.html page. Thanks.
Thanks.

Modifying website through email

I wanted to try to do something where I can make changes to my site based on emails received to the sites email. Does anyone know how to do that?
I want something where if its from a specific email address, and follows a particular format, it will change a certain part of the site according to the contents of the email.
Sure you can.
You need:
Script that parses emails on schedule and adds those changes to some type of database
Script that executes updates from the temporary database and adds that data to live database
How to read email with PHP:
$mb = imap_open("{host:port/imap}","username", "password" );
$messageCount = imap_num_msg($mb);
for( $MID = 1; $MID <= $messageCount; $MID++ )
{
$EmailHeaders = imap_headerinfo( $mb, $MID );
$Body = imap_fetchbody( $mb, $MID, 1 );
doSomething( $EmailHeaders, $Body );
}
read more
You would need some server side processing to be able to do this. This thread has a couple of ways of doing this using PHP, made easier with cPanel to make changes to the mail redirects. If you tell us more about your site and hosting environment, we may be able to give better suggestions.
The server side script would need to then parse your email and perform whatever updates your commands intend. Security is also very important, and it is trivial to forge the 'From' address when sending email.

Categories