4

I am currently building a RESTful API in PHP which will be used to power both a web and mobile application. As such, the API should be considered public. However, I want to authenticate users when they make requests to the API from either mobile or web.

When a user registers for the service, I generate a unique API key and secret which is stored against the user's record in the database. Ideally, I want requests to be made only using the API key using JavaScript or PHP.

I thought about establishing a hand-shake system for authentication, with the process as follows:

  • User makes request to the API using their known key.
  • The API responds with a token which is stored in a table along with a timestamp (to prevent replay attacks)
  • The token is then used to make the actual request by the client, and the API will check the validity of the token. If the hash matches, and the timestamp is valid, the API provides a valid response, otherwise provides information that the token is expired.

I was thinking to use HMAC to generate the token, something like this:

$token = hash_hmac('sha256', $user->apiKey.microtime(), $user->apiSecret);

The $token will then be stored in the DB, and should be unique for every request. In turn the requests can then be made using the following jQuery code:

$.getJSON('/api/user/get/1', { 'key': '123rrwfnufsd7f72' }).done(function(data) { 
    // data will now contain a token, so we use it to make another AJAX request:
    if( data.token )
    {
        $.getJSON('/api/user/get/1', { 'token': data.token }).done(function(user) {  
             // do something with user or handle bad token
        });
    }
});

My question is will this be sufficient to prevent brute-force and replay attacks?

1
  • 1
    You are on the right track. I generally use ssl, and rotating tokens which expire and can be renewed, this is common and easy to implement. Read up about authentication protocols such as Oauth to get an understanding of pitfalls and benefits. Commented Feb 24, 2017 at 9:56

1 Answer 1

4

I just crreated a small lib about this. You can specify a time drift in minutes if need, or even use a custom salt. ( see https://github.com/gboddin/psk-validator/ )

This library allows you to authenticate signed message from a client using time based salts.

Installation

composer require gboddin/psk-validator

Usage

Client

$sharedsecret = '43223ff65b6ce17072cda5729b20daceec611d1f39e76040d347ceeca51d2a47';
$data = json_encode(['suff','otherstuff',['machin' => 'bidule']]);

/**
 * Client :
 * Invoke the validator with the pre-shared key and an algo  (sha256 by default) and
 * define an allowed time drift in minutes ( 2 by default ).
 */
$sigValidation = new \Gbo\PSKValidator($sharedsecret, 'sha256');
/**
 * Signs a bunch of data and get the signature.
 * The second optional parameters allows for a user provided salt instead
 * of the default time based salt. It must be agreed on between client and server.
 */
$signature =  $sigValidation->sign($data, null);

Server

/**
 * Server :
 * The optional third parameter allows to define a maximum time drift  in minutes ( default 2 minutes )
 */

$signature =  $httpRequest->getHeader('x-signature');
$sharedsecret = '43223ff65b6ce17072cda5729b20daceec611d1f39e76040d347ceeca51d2a47';
$sigValidation = new \PSKValidator($sharedsecret, 'sha256', 2);
$data = $httpRequest->getBody();

/**
 * Server :
 * The third optional parameters allows for a user provided salt instead
 * of the default time based salt. It must be agreed on between client and server.
 */

$signatureIsValid = $sigValidation->verify($data, $signature, null);

var_dump(
    $data,
    $signature,
    $sigValidation->getTimeBasedSignatures($data),
    $signatureIsValid
);

Server output

string(41) "["suff","otherstuff",{"machin":"bidule"}]"
string(64) "d85a2d6873e034cb3ab8c490cb82139d8dabae6c08581cca0a2e7497ead287a4"
array(5) {
  [0]=>
  string(64) "d85a2d6873e034cb3ab8c490cb82139d8dabae6c08581cca0a2e7497ead287a4"
  [1]=>
  string(64) "dc150239c61fe272b7ca44ad0918d159a84e5bc1661db48bad04a81bc7f4c742"
  [2]=>
  string(64) "e1822fc6cc7bbf1184b29efaaaceac6d598fb406b4f8cf9b3717b3d0c533c19f"
  [3]=>
  string(64) "d85a2d6873e034cb3ab8c490cb82139d8dabae6c08581cca0a2e7497ead287a4"
  [4]=>
  string(64) "d85a2d6873e034cb3ab8c490cb82139d8dabae6c08581cca0a2e7497ead287a4"
}
bool(true)

( sorry for the self promotion :) )

Sign up to request clarification or add additional context in comments.

1 Comment

Please correct me if I am wrong; firebase/php-jwt also can do same authentication(with expiry). So you have pre-shared client secret(key) as user id + pwd of a user in db and then you send salt to api(uid /pwd in jwt from login form); then after success validation you will recieve signatureIsValid (jwt token with expiry); right ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.