Private API Creation Standards - Is this API correct?

353 views Asked by At

So we wanted to create a private API that would allow others to interact with our services such as saving certain things in our db via api or getting certain information.

The API itself only expects JSON data that can be pased via any programming language to the API. Unlike other APIs like facebook, we don't give any files to users to use API like facebook does provide classes for different languages to interact with their api. So we thought api users can use any language to send us json data and based on that json data, we can call certain functions of api via use parameter they provide via query string.

Here is code of the API at most basic level to get you an idea of how it works:

class Api
{
    /**
     * Default function called when API url is accessed
     *
     * @return mixed
     * @throws Exception
     */
    public function index()
    {
        $appId = isset($_GET['appId']) ? $_GET['appId'] : null;
        $apiKey = $_GET['apiKey'] ? $_GET['apiKey'] : null;
        $method = $_GET['use'] ? $_GET['use'] : null;

        $data = json_decode(file_get_contents("php://input"), true);

        if ($this->validateRequest($appId, $apiKey, $method)) {
            return call_user_func_array(array($this, $method), array($data));
        }
    }

    private function validateRequest($appId, $apiKey, $method)
    {
        # check referer
        $requestUri = isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : null;

        if (!$requestUri) {
            $this->bye('HTTP_REFERER is required!');
        }

        # check if method exists
        if (!$method || !method_exists('Apicall', $method)) {
            $this->bye('Invalid \'use\' value!');
        }

        # check appId, apiKey and requestUri
        if (!$appId) {
            $this->bye('Invalid appId!');
        }

        if (!$apiKey) {
            $this->bye('Invalid apiKey!');
        }

        $db = new DB();
        $db->where('appid', $appId);
        $db->where('apikey', $apiKey);
        $db->where('request_uri', pathWithoutEndingSlash($requestUri));
        $db->get();

        if ($db->exists() && $db->result_count() > 0) {
            return true;
        }

        $this->bye('Request Denied!');
    }

    private function bye($error)
    {
        echo json_encode(array('Error' => $error));
        exit;
    }

    private function getIntervals()
    {
        $values = array('monthly', 'quarterly', 'yearly', 'single_payment');
        echo json_encode($values);
    }
}

As can be seen, we validate if provided appId, apiKey and requestURI exists in our db (which we already generated for api users) then call the method specified via the use query string variable in the call:

http://localhost/myapi/api.php?appId=local&apiKey=98a4d2e5b7fa21d424a0932c7f47a6943d57d64b&use=getIntervals

Is this correct way of making a private API or am I still missing something ?

1

There are 1 answers

0
Luceos On BEST ANSWER

Anyone who listens on requests (so if you call your api on WIFI or as AJAX calls) will be able to see your appId and apiKey. So they will be able to call your api as well. A better method is to:

  • a) use SSL on your API server to encrypt calls (POST'ing to SSL from non-SSL already ensures encryption)
  • b) use POST or headers to send your authentication data instead of adding it in the URL
  • c) have the client log in once and provide a token for the current session which is allowed to connect during x minutes or is discarded after x minutes of the last call
  • d) lock the token (from c) to a client IP, if another IP connects with the same token, invalidate the token

If you want a quick REST API I can recommend Restler. It also incorporates authentication. http://luracast.com/products/restler