REST API Authentication

I’m in the process of finishing up an API I have created for clients to interface with. Currently this API is fairly basic and is eventually expected to add more functionality in future versions. As of right now though my API only has one get and one post option. For the sake of an example that everyone can follow I will use books.

Currently you can do a GET request to api.domain.com/v1/book/ and it will return a xml response of a list of current books in the system. The POST request sent to api.domain.com/v1/book/ will add a new book to the catalog. To expand on the POST request the data being posted is an array containing an index of xml with a value of an XML request.

Now I don’t really care if someone can do a GET request and obtain information but I do want to lock down the POST requests to only accept input from authorized sources. This is where I’m stuck. I’m trying to find a way to authenticate the client connecting. I have looked at HTTP Auth Basic and I wasn’t to thrilled with the idea of using plain text usernames and passwords even though all data is transmitted over an SSL connection. I also looked at OAuth and while it does sound like it could work it looks like for a basic client/server relationship it might be a bit to much. Maybe I’ve just not seen the right example of OAuth for client/server. My next thought was to have the client send over an API key assigned to them and in this key would be the encrypted value of their domain name along with their client id. On the server I would decrypt the api key and first verify the source in the key matches the one sending the key. If that checks out then I would verify that the supplied client id is in our authorized list of users. Should that pass then their request would be accepted.

Pseudo Code

if(isset($_POST['apikey'])){
    $arraykey = decryptKey($_POST['apikey']);
    if($arraykey['domain'] == $_SERVER['REMOTE_HOST']){
        if(validClient($arraykey['clientid'])){
            // Process request
        } else {
            sendResponse(401 not authorized)
        }
    } else {
        sendResponse(401 not authorized)
    }
} else {
    sendResponse(401 not authorized)
}

Does anyone see a potential problem with this kind of authentication?

Using $_SERVER[‘REMOTE_HOST’] can be problematic.

A client IP address can change quite frequently, even between requests.

I will probably opt for a different way to check the client IP and or hostname. Since most $_SERVER variables are unreliable for getting an IP or hostname and could potentially be forged. Is there a way to avoid using $_SERVER variables to check the validity of the remote client?

What if any security issues are there with this way of authenticating the remote client? One thing that sticks out in the back of my mind is how do people handle the case where an API Key gets in the wrong hands? Is there anyway to protect an API when that type of thing happens? For instance lets say someone decides to have their server connect to the API using javascript and the API key is in plain view for any of their customers to see.

You can use Digest authentication, if you don’t want to transmit user/pass in clear text (understandably).

See http://www.peej.co.uk/projects/phphttpdigest.html

I’d look into implementing a HMAC system to “sign” requests with an API key.

http://php.net/manual/en/function.hash-hmac.php

Basically, you figure out some way of storing the request the client is trying to make in a string along with the current time/date and generate a HMAC value of that request (Include time/date in the data you use to compute the HMAC) with an API key being used as the secret key. The client then sends the request, along with the computed HMAC, some kind of public key to identify the user making the request and the client’s time/date. Then when you get the request along with the HMAC send by the client and the client’s time/date you check to make sure the time has not deviated by much and then you compute what you expect the HMAC to be (Using the public key to identify the user suppose to be making the request and enabling you to lookup the private key stored somewhere on your server), again using the private API key as the secret key, and if they match then you know the request is authorized.

This way, even across a unencrypted SSL connection, the private API key is never sent and the public key you send is useless without the appropriate private key to “sign” the request. Also the signed request is only good for a limited time frame since the time/date of the request is also included in the HMAC hash. Furthermore, its subtable for use in javascript as you only need to provide the browser with a HMAC hash and the public key.

Sorry if this is not explained to well :frowning:

This sounds similar to the way Amazon does it for their S3 API. I kind of understand the idea behind it but I tend to get lost.

So basically let me see if I got this right.

Generate a HMAC Hash by doing something that amounts to the following

$hash = hash_hmac('ripemd160', $xml.$time.$clientId, 'secret');

Then say I am submitting the following array of data via POST.

Array
(
    [xml] => '<request>Some XML Data Here</request>'
    [expires] => 'Unix Timestamp?'
    [clientId] => '12345'
    [hash] = > 'b8e7ae12510bdfb1812e463a7f086122cf37e4f7'
)

On the server side I would then do something along the lines of the following

$key = lookupSecret($_POST['clientId']);
$hash = hash_hmac('ripemd160', $_POST['xml'].$_POST['time'].$_POST['clientId'], $key);
if($hash === $_POST['hash']){
    // proceed
} else {
    sendResponse(401,'Unauthorized');
}

Of course there would be more to it than the above. Am I on the right track?

Yep, that is correct. Although you’d need to make sure you check the expiry time on the request. But since its unlikely that both the client and server have the exact same time you’ll need to allow for some deviation from the time the client send to you. By how much is up to you really - depends how secure you want it to be.

Thanks for the help everyone. :slight_smile: