web-dev-qa-db-ja.com

AJAXおよびPHPによるサードパーティAPIへのセキュアなAPIコール

GET、POST&PUTを呼び出してサードパーティAPIを呼び出し、AJAX経由でクライアント側に応答を表示したい。API呼び出しにはトークンが必要ですが、そのトークンを秘密にしておく必要があります/クライアント側のJSコードにはありません。

私はいくつかの このような提案 を見て、AJAXによってクエリされ、実際のAPI呼び出しを処理するサーバー側のコードを途中に配置しました。 AJAXのAPIを直接操作しても問題ありませんが、ユーザーからトークンを隠すために2段階のプロセスをどのように処理するかわかりません。私のグーグルは、これを達成するためのベストプラクティスの方法に関する指針を明らかにしていません。

私の場合、真ん中のサーバーはPHPを実行しているので、cURL/GuzzleがトークンでAPI呼び出しを行う簡単なオプションであると思います。 API応答はJSONになります。

誰でも、jQuery.ajax()を使用してPHPからサードパーティのAPIにこれを実現する大まかな例を教えてもらえますか?

あるいは、この方法を詳細に説明している品質のリソースがある場合は、リンクをいただければ幸いです。同様に、これがひどい方法である場合、その理由を知るのは素晴らしいことです。

編集
おそらく、これをできるだけ柔軟に展開できるようにしたいと思います。独自の構成を持つ複数のサイトで使用されるため、サーバーまたはホスティングアカウントの構成を変更せずに実装するのが理想的です。

10
t-jam

サンプルコードなしでは少し難しいです。しかし、私はあなたがこれに従うことができることを理解したように、

AJAX CALL

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

            }
        });

PHPの場合

投稿されたデータを収集して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 );

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;

 }
}
7
MMRahman

トークンをhttp headersに追加するだけなので、Authorizationであると想定していますが、単純な方法は、それらを追加した後にAPIエンドポイントを呼び出すプロキシサーバーを実装することです。 nginxのサンプルファイルは

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

これは、プログラムを作成するよりもはるかに優れたアプローチであり、4行のコードを用意します。パラメータを適宜変更し、使用しているAPIクライアントの必要に応じて他のパラメータを追加してください。 JavaScript側の唯一の違いは、プロキシとして機能するサービスによって提供されるものではなく、location urlを使用することです。

編集する

Apacheの構成は次のようになります

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

要件からは、「サーバー側のコードを途中で」relay(proxy)スクリプトが最適なオプションのように見えます。

PHPの例はこちら 。 N.B. CURLエラーを処理するために、['status']( 'OK'またはCURLの失敗に関する情報)とAPIプロバイダーからの実際の応答を含む['msg']で構成される新しい「オブジェクト」を返します。 JSでは、元のAPI「オブジェクト」は「msg」の下の1レベル下に抽出する必要があります。

基本的なリレー/プロキシを回避できます

リレースクリプトを使用する場合、APIキーを探している誰かがおそらくを他の場所で試します。しかしながら;海賊は、APIキーを使用したAPIプロバイダーへの呼び出しを、スクリプトへの呼び出しに置き換えるだけで済みます(APIキーは引き続き使用されます)。

検索エンジンボットによるAJAX /リレースクリプトの実行

Googleボット(その他?)はAJAXを実行します。 I仮定(リレーかどうか)AJAXがユーザー入力を必要としない場合、ボットにアクセスするとAPIキーが使用されます。ボットは " 「将来(現在?)、ユーザー入力をエミュレートする可能性があります。たとえば、ドロップダウンから都市を選択するとAPIリクエストが発生する場合、Googleはドロップダウンオプションを循環させる可能性があります。

懸念がある場合は、リレースクリプトにチェックを含めることができます。

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

上記の問題に対応するためのリレースクリプトと追加コード

海賊の保護を無理しないでください。リレーは高速で、訪問者が気付かないほど遅延する必要があります。考えられる解決策(専門家がいないため、セッションに錆びがある):

1:PHPセッションソリューション

AJAX=過去15分間にページにアクセスし、有効なトークンを提供し、同じユーザーエージェントとIPアドレスを持っている人がリレーを呼び出しているかどうかを確認します。

Your Ajax Pages次のスニペットを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);} });

リレー/プロキシスクリプト(セッションバージョン):

既存のリレースクリプトの例 を使用し、CURLブロックの前に以下を追加します。

  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
  }

標準のセッションini設定を想定しています。同じドメインでのCookieとページ/リレーが必要(回避策が可能)。セッションはパフォーマンスに影響を与える可能性があります。サイトがすでにセッションを使用している場合、コードはこれを考慮する必要があります。

2:Sessionless/Cookieless option

特定のIPアドレスとユーザーエージェントに関連付けられたトークンを使用し、最大2時間有効です。

ページとリレーの両方で使用される関数 "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既存の例 を使用し、CURLブロックの前に以下を追加します。

// 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

API(または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);} });

注:ユーザーエージェントは偽装可能です。 IP(REMOTE_ADDR)は偽造できませんが、少数のサイトで設定すると問題が発生する可能性があります。 NGINXの背後にいる場合、REMOTE_ADDRには常にNGINXサーバーIPが含まれていることがあります。

APIキーの使用上限に達するまで機密ではない情報を提供する一般的なサードパーティAPIを使用している場合は、上記のソリューションで十分だと思います。

4
scytale

人々が指摘したように、サーバーのプロキシメソッドでAPIキーを非表示にする必要があります。

サーバーでのメソッドの誤用を防ぐには、サーバーから生成された(JavaScriptでは使用しない)ワンタイムトークン(通常はフォームに使用する)で呼び出しを保護します。

私は、上記のコード化された既知のhttpユーザーエージェントまたはサイトトークンをチェックするコードのファンではありません。これは安全ではありません。

2
michael - mlc

CUrlを使用する場合、保護する必要があるのはサーバーです。私が個人的に使用する方法は、あなたのような問題を整理するために作られたGoogle reCaptchaです。ここでは、クライアント側とサーバー側の統合について、順を追って説明しました。 https://webdesign.tutsplus.com/tutorials/how-to-integrate-no-captcha-recaptcha-in-your-website--cms-23024 この方法では、あなたがする必要はありませんvirtualhostファイルとApache構成のすべてを変更します。

1
Luis Gar

ユーザーがログインに一意のIDを生成させ、サーバーセッションとCookieに保存したときにできることは、バックエンドとフロントエンドの間にセキュリティレイヤーを追加する場合、公開されたソリューション@MMRahmanを使用します。ブラウザのローカル/セッションストア。このようにして、バックエンドをajaxで呼び出すと、ブラウザに保存した場所から値を取得し、値が同じかどうかを確認できます。そうであれば、外部APIを呼び出し、要求を単に無視しない場合は、値を返します。

つまり、ユーザーログイン->一意のIDを生成->サーバーセッションとブラウザセッションに保存->ブラウザセッションからの値をパラメータとして渡してajaxから呼び出し->サーバーセッションに保存された値と一致するかどうかをチェック->はい呼び出しの場合バックエンドに保存されたトークンを使用する外部API/db /ファイル/必要なもの

1
Emilio Camacho