jeremykendall / query-auth-impl

Example implementation of query-auth library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Query Auth Example Implementation

Example implementation of the Query Auth library

Requirements

In order to run this example implementation, you'll need to have the following installed:

Usage

  • Clone repo
  • cd /path/to/repo
  • Run vagrant up
  • Add 192.168.56.102 query-auth.dev to /etc/hosts
  • Open a browser and visit http://query-auth.dev

Examples

All code samples below can be found in /public/index.php.

Using Slim Middleware to Validate Request Signatures

Since this example implementation includes multiple routes that require signature validation, I decided to use Slim Framework's Route Middleware so that I'd only have to write the code once. When you see $validateSignature attached to the /api/* routes below, know that the validation is being performed by the middleware before the code in those routes is being executed.

// Middleware to validate incoming request signatures
// See http://docs.slimframework.com/#Route-Middleware
$validateSignature = function (Slim $app, RequestValidator $requestValidator) {
    return function () use ($app, $requestValidator) {
        $response = $app->response;

        try {
            // Grabbing credentials from app container kind of mimics grabbing
            // credentials from persistent storage
            $credentials = $app->credentials;

            $isValid = $requestValidator->isValid(
                new SlimRequestAdapter($app->request),
                $credentials
            );

            if ($isValid === false) {
                $jsend = new JSendResponse('fail', array('message' => 'Invalid signature'));
                $response->setStatus(403);
                $response->headers->set('Content-Type', 'application/json');
                $response->setBody($jsend->encode());
            }
        } catch (\Exception $e) {
            $jsend = new JSendResponse('error', array(), $e->getMessage(), $e->getCode());
            $response->setStatus(403);
            $response->headers->set('Content-Type', 'application/json');
            $response->setBody($jsend->encode());
        }
    };
};

GET Example

Client: Sending a Signed GET Request

Visit http://query-auth.dev/get-example to see an example of a signed GET request:

/**
 * Sends a signed GET request which returns a famous mangled phrase
 */
$app->get('/get-example', function () use ($app, $requestSigner) {

    // Create request
    $guzzle = new GuzzleHttpClient(['base_url' => 'http://query-auth.dev']);
    $request = $guzzle->createRequest('GET', '/api/get-example');

    // Sign request
    $requestSigner->signRequest(new GuzzleHttpRequestAdapter($request), $app->credentials);

    // Send request
    try {
        $response = $guzzle->send($request);
    } catch (BadResponseException $bre) {
        $response = $bre->getResponse();
    }

    // Render template with data
    $app->render('get.html', array('request' => (string) $request, 'response' => (string) $response));
});

Server: Handling a GET Request

Uses $validateSignature to ensure the request signature is valid.

  • If not valid, return the response generated by $validateSignature.
  • If valid, return the famous mangled phrase.

Below is the /api/get-example GET route of the sample implementation:

/**
 * Validates a signed GET request and, if the request is valid, returns a
 * famous mangled phrase
 */
$app->get('/api/get-example', $validateSignature($app, $requestValidator), function () use ($app) {

    $response = $app->response();

    // If client error (400 - 499) because signature is invalid, return response
    if ($response->isClientError()) {
        return $response;
    }

    $mistakes = array('necktie', 'neckturn', 'nickle', 'noodle');
    $format = 'Klaatu... barada... n... %s!';
    $data = array('message' => sprintf($format, $mistakes[array_rand($mistakes)]));
    $jsend = new JSendResponse('success', $data);

    $response->headers->set('Content-Type', 'application/json');
    $response->setBody($jsend->encode());

    return $response;
});

POST Example

Client: Sending a Signed POST Request

Visit http://query-auth.dev/post-example to see an example of a signed POST request:

/**
 * Sends a signed POST request to create a new user
 */
$app->get('/post-example', function () use ($app, $requestSigner) {

    $params = array(
        'name' => 'Ash',
        'email' => 'ash@s-mart.com',
        'department' => 'Housewares',
    );

    // Create request
    $guzzle = new GuzzleHttpClient(['base_url' => 'http://query-auth.dev']);
    $request = $guzzle->createRequest('POST', '/api/post-example', ['body' => $params]);

    // Sign request
    $requestSigner->signRequest(new GuzzleHttpRequestAdapter($request), $app->credentials);

    // Send request
    try {
        $response = $guzzle->send($request);
    } catch (BadResponseException $bre) {
        $response = $bre->getResponse();
    }

    $app->render('post.html', array('request' => (string) $request, 'response' => (string) $response));
});

Server: Handling a POST Request

Uses $validateSignature to ensure the request signature is valid.

  • If valid, save the new user and return the new user data.
  • If not valid, return the response generated by $validateSignature.

Below is the /api/post-example POST route of the sample implementation:

/**
 * Validates a signed POST request and, if the request is valid, mimics creating
 * a new user
 */
$app->post('/api/post-example', $validateSignature($app, $requestValidator), function () use ($app) {

    $response = $app->response();

    // If client error (400 - 499) because signature is invalid, return response
    if ($response->isClientError()) {
        return $response;
    }

    $params = $app->request()->post();

    // Assume appropriate POST action of some sort, in this case saving
    // a new user and returning the persisted user data.
    $data = array(
        'user' => array(
            'id' => 666,
            'name' => $params['name'],
            'email' => $params['email'],
            'department' => $params['department'],
        ),
    );

    $jsend = new JSendResponse('success', $data);

    $response->headers->set('Content-Type', 'application/json');
    $response->setBody($jsend->encode());

    return $response;
});

Replay Prevention Example

Client: Sending a Signed POST Request OR Replaying a Previous Request

Visit http://query-auth.dev/replay-example to see an example of replay prevention:

/**
 * Sends a signed POST request to create a new user, OR replays a previous POST request
 */
$app->map('/replay-example', function () use ($app, $requestSigner) {

    // Create request
    $guzzle = new GuzzleHttpClient(['base_url' => 'http://query-auth.dev']);
    $request = $guzzle->createRequest('POST', '/api/replay-example');

    // Build a new request
    if ($app->request()->isGet()) {

        $params = array(
            'name' => 'Ash',
            'email' => 'ash@s-mart.com',
            'department' => 'Housewares',
        );

        $request->getBody()->replaceFields($params);

        // Sign request
        $requestSigner->signRequest(new GuzzleHttpRequestAdapter($request), $app->credentials);
    }

    // Build a replay request
    if ($app->request()->isPost()) {
        // Set a previous request's data on a new request
        $request->getBody()->replaceFields($app->request->post());
    }

    // Send request
    try {
        $response = $guzzle->send($request);
    } catch (BadResponseException $bre) {
        $response = $bre->getResponse();
    }

    $app->render('replay.html', array(
        'request' =>  (string) $request,
        'response' => (string) $response,
        'postFields' => $request->getBody()->getFields(),
    ));
})->via('GET', 'POST');

Server: Handling a Replayed Request

Uses $validateSignature to ensure the request signature is valid.

  • If valid, save the API key, request signature, and signature expiration timestamp
    • If the save is successful, this is a new request
    • If the save is unsuccessful, this is a replayed request and is denied
  • If not valid, return the response generated by $validateSignature.

Below is the /api/replay-example POST route of the sample implementation:

/**
 * Uses $validateSignature to ensure the request signature is valid.
 * If valid, save the API key, request signature, and signature expiration timestamp
 *     If the save is successful, this is a new request
 *     If the save is unsuccessful, this is a replayed request and is denied
 * If not valid, return the response generated by `$validateSignature`.
 */
$app->post('/api/replay-example', $validateSignature($app, $requestValidator), function () use ($app, $config) {

    $response = $app->response();

    // If client error (400 - 499) because signature is invalid, return response
    if ($response->isClientError()) {
        return $response;
    }

    try {
        $db = new \PDO(
            $config['pdo']['dsn'],
            $config['pdo']['username'],
            $config['pdo']['password'],
            $config['pdo']['options']
        );

        $params = $app->request()->post();

        $signatureDao = new SignatureDao($db);
        $signatureDao->save($params['key'], $params['signature'], (int) gmdate('U') + 3600);

        // Assume appropriate POST action of some sort, in this case saving
        // a new user and returning the persisted user data.
        $data = array(
            'user' => array(
                'id' => 666,
                'name' => $params['name'],
                'email' => $params['email'],
                'department' => $params['department'],
            ),
        );

        $jsend = new JSendResponse('success', $data);
    } catch (\PDOException $pe) {
        if ($pe->getCode() == 23000) {
            $response->setStatus(403);
            $jsend = new JSendResponse('error', array(), sprintf('REPLAYED REQUEST: %s', $pe->getMessage()), $pe->getCode());
        }
    } catch (\Exception $e) {
        $response->setStatus(400);
        $jsend = new JSendResponse('error', array(), $e->getMessage(), $e->getCode());
    }

    $response->headers->set('Content-Type', 'application/json');
    $response->setBody($jsend->encode());

    return $response;
});

Credits

This example implementation makes use of the following external dependencies:

About

Example implementation of query-auth library

License:MIT License


Languages

Language:Puppet 47.2%Language:Ruby 38.5%Language:Shell 6.3%Language:Perl 5.2%Language:PHP 2.3%Language:CSS 0.3%Language:Pascal 0.1%