web-dev-qa-db-fra.com

Appels API sécurisés avec AJAX et PHP vers une API tierce

Je souhaite effectuer des appels GET, POST & PUT vers une API tierce et afficher la réponse côté client via AJAX. Les appels d'API nécessitent un jeton, mais je dois le garder secret/non dans le code JS côté client.

J'ai vu quelques suggestions comme celle-ci avoir un code côté serveur au milieu qui serait interrogé par AJAX et gérerait l'appel d'API réel. Je peux travailler directement avec l'API d'AJAX, mais je ne suis pas sûr de savoir comment utiliser un processus en deux étapes pour masquer le jeton aux utilisateurs. Ma recherche sur Google n’a donné aucune indication sur la meilleure méthode à suivre pour atteindre cet objectif.

Dans mon cas, le serveur au milieu utiliserait PHP. Je suppose donc que cURL/Guzzle est l’option la plus simple pour passer les appels d’API avec le jeton. Les réponses de l'API seront JSON.

Quelqu'un peut-il me donner, s'il vous plaît, un exemple approximatif de la manière dont cela pourrait être réalisé en utilisant jQuery.ajax (), en PHP, à l'API tierce?

Sinon, s'il existe des ressources de qualité couvrant cette méthode en détail, j'apprécierais un lien. De même, si cette méthode est terrible, il serait bon de savoir pourquoi.

Modifier
Il est probablement intéressant de noter que je veux autant de souplesse que possible pour déployer ceci; il serait utilisé sur plusieurs sites avec des configurations uniques. Idéalement, cela serait implémenté sans modifier la configuration du serveur ou du compte d'hébergement.

8
t-jam

C'est un peu difficile sans exemple de code. Mais selon ce que j'ai compris, vous pouvez suivre cela,

AJAX CALL

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

            }
        });

Dans PHP

Recueillez vos données postées et gérez l'API. 

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


 $api = new Api();
 $api->PostMyData($data );

Exemple de classe API

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;

 }
}
6
MMRahman

Parce que tout ce que vous voulez, c'est ajouter un jeton à http headers, ce qui, je suppose, est Authorization. Un moyen simple serait de mettre en œuvre un serveur proxy qui effectue des appels vers votre point de terminaison api après leur ajout. Un exemple de fichier pour nginx serait

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

Il s’agit d’une approche beaucoup plus intelligente que d’écrire un programme et vous permet de démarrer avec 4 lignes de code. Assurez-vous de modifier vos paramètres en conséquence et d’ajouter d’autres paramètres en fonction des besoins du client api que vous utilisez. La seule différence du côté javascript serait d'utiliser le location url plutôt que celui fourni par le service qui fait office de proxy.

Modifier

La configuration pour Apache serait

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
georoot

Selon vos besoins, il semble que le meilleur script est le script de relais (proxy) "côté serveur au milieu". 

_ { PHP exemple ici } _. N.B. pour gérer les erreurs CURL, il renvoie un nouvel "objet" comprenant ['status'] ('OK' ou des informations sur l'échec de CURL) et ['msg'] contenant la réponse réelle du fournisseur d'API. Dans votre JS, l '"objet" d'origine de l'API nécessiterait maintenant l'extraction d'un niveau sous "msg".

Les relais/mandataires de base peuvent être contournés

Si vous utilisez un script de relais, alors quelqu'un qui recherche une clé API va probablement essayer ailleurs. Toutefois; le pirate pourrait simplement remplacer son appel au fournisseur d’API en utilisant votre clé d’API par un appel à votre script (et votre clé d’API sera toujours utilisée).

Exécution de votre script AJAX/relay par les robots des moteurs de recherche

Google bots (autres?) Exécutent AJAX. Je suppose (relais ou non) si votre AJAX n'a pas besoin de la saisie de l'utilisateur, les visites de bot entraîneront l'utilisation de la clé API. Les bots "s'améliorent". À l’avenir (maintenant?), Ils pourraient émuler les entrées de l’utilisateur, par exemple. si la sélection d'une ville à partir d'une liste déroulante entraîne une demande d'API, Google peut alors parcourir les options de liste déroulante. 

Si vous vous inquiétez, vous pouvez inclure une vérification dans votre script de relais, par exemple.

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

Script de relais et code supplémentaire pour répondre aux problèmes ci-dessus

Ne pas abuser de la protection des pirates; les relais doivent être rapides et inaperçus pour les visiteurs. Solutions possibles (pas d'expert et rouillé avec les sessions):

1: PHP solution de sessions

Vérifie si le relais est appelé par une personne qui a visité votre page AJAX au cours des 15 dernières minutes, qui a fourni un jeton valide et qui possède le même agent d'utilisateur et la même adresse IP. 

Vos pages Ajax ajoutez les extraits suivants à votre 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);} });

Le script relais/proxy (version de session):

Utilisez un exemple de script de relais existant et avant le bloc CURL, ajoutez:

  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
  }

Suppose que les paramètres standard de la session sont ini. Cookies requis et page/relais sur le même domaine (solution possible). Les sessions peuvent avoir un impact sur les performances. Si le site utilise déjà des sessions, le code devra en tenir compte.

2: Option sans session/sans cuisson

Utilise un jeton associé à une adresse IP et à un agent d'utilisateur spécifiques, valide pour un maximum de 2 heures.

Fonctions utilisées à la fois par la page et le relais, par exemple. "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 Utilisez un exemple existant et avant le bloc CURL, ajoutez:

// 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 nécessitant l'API (ou votre fichier d'inclusion javascript):

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

Remarque: l'agent utilisateur est spoofable. IP (REMOTE_ADDR) "ne peut pas" être falsifié, mais la configuration sur une minorité de sites peut causer des problèmes, par exemple. si vous êtes derrière NGINX, vous trouverez peut-être que REMOTE_ADDR contient toujours l'adresse IP du serveur NGINX. 

Si vous utilisez une API tierce typique qui fournira des informations NON sensibles jusqu'à ce que vous atteigniez le plafond d'utilisation de votre clé d'API, les solutions ci-dessus (je pense) devraient suffire.

4
scytale

Comme l'ont souligné d'autres personnes, vous souhaitez qu'une méthode proxy sur votre serveur masque la clé API.

Pour éviter toute utilisation abusive de votre méthode sur le serveur, protégez l'appel avec un jeton ponctuel (comme celui que vous utilisez habituellement pour les formulaires) - généré à partir de votre serveur (pas en javascript ..).

Je ne suis pas un fan du code collé ci-dessus qui vérifie la présence d'agents d'utilisateurs http connus ... ou de jetons de sites ... ce n'est pas sécurisé.

2
michael - mlc

Si vous utilisez cUrl, vous devez protéger votre serveur. Personnellement, j’utilise le logiciel Google reCaptcha, conçu pour régler des problèmes comme le vôtre. Très bien expliqué l’intégration étape par étape ici du côté client et côté serveur. https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 De cette façon, vous ne le ferez pas pas besoin de changer quoi que ce soit dans vos fichiers virtualhost et toutes les configurations Apache.

1
Luis Gar

J'utiliserais la solution publiée @MMRahman, si vous souhaitez ajouter une couche de sécurité entre votre serveur et votre interface, ce que vous pouvez faire lorsque l'utilisateur demande à son identifiant de générer un identifiant unique, de le stocker dans la session du serveur et dans un cookie ou magasin local/de session du navigateur. Ainsi, lorsque vous appelez votre backend avec ajax, vous pouvez obtenir la valeur à partir de l’emplacement où vous avez enregistré le navigateur et vérifier si les valeurs sont identiques, si oui, vous appelez l’API externe et renvoyer les valeurs sinon ignorer simplement la demande.

En résumé: Identifiant utilisateur -> générer un identifiant unique -> le stocker dans la session du serveur et la session du navigateur -> faire un appel depuis ajax en passant en paramètre la valeur de la session du navigateur-> si oui, appelez une API externe en utilisant le jeton stocké dans votre backend/db/fichier/comme vous le souhaitez 

0
Emilio Camacho