Similar Articles

Building a Secure API - Part 2

This post is part 2 in the Building a Secure API series. Click here for more articles in the series!

In the previous part of this series I took a bit of time to introduce the basic concepts and the process we were going to implement to create our secure API. I also introduced some of the tools we'd be using to create our API. If you haven't read through that first part of the series, I'd definitely recommend it before continuing on. In this second part of the series I'm going to begin digging into those tools, starting with the Slim Framework.

I'm starting with the framework as it's going to be our foundation that everything is built on. All of our routes and functionality will be contained inside of its structure. There are a lot of frameworks out in the PHP ecosystem right now to choose from. I went with Slim because, in my opinion, it offers the lowest barrier for entry and doesn't require much explanation as everything can be laid out in one file if desired.

Since this is a multi-part series, I wanted to be sure that there was plenty of clarity around the code being presented. I've created a repository you can use to keep track of the changes happening in each part of the process. The repository contains branches for each part of the series, each building on the functionality from the previous part. They'll be showing the end result of each of the tutorials in the series so you can follow along with each tutorial and see how it builds up or just use the repository to view the "end game".

One other thing I will mention about the code we'll being producing - I'll be making heave use of several PHP packages so we're not recreating a lot of code that's already been written and tested. Because of this you'll need to be familiar with the Composer dependency management tool. We'll be using this heavily to pull in packages and make them easily available to our application.

Enjoying the article? Consider contributing to help spread the message!

What About a Webserver?

While you can set up the web server of your choice to handle the requests coming in to the Slim application, I'm going to keep things simple here. PHP has its own built-in web server that's perfect for examples like this. It allows you to define and index.php "front controller" for your application and serves it up on the port of your choice.

One thing to note here - while I'm using the built-in PHP server for my examples it shouldn't be used as a replacement for a more robust web server like Apache or Nginx. It's great for testing and local development but it definitely leaves something to be desired in the performance department. Moral of the story? If you use the built-in PHP web server in production you're going to have a bad time.

Lets get a simple script up and running with the built-in server so you can see how it works. You can create our test file anywhere on your system just so long as there's only one index.php file in the directory. Usually I recommend making a temporary directory if you're going to try this out. I'm going to assume commands on a Unix-based system for these tutorials but the process on Windows is similar.

It's just a few simple commands to create the file:

mkdir www-tmp
echo '<?php phpinfo(); ?>' test.php

We then have a PHP file in test.php that contains a call to the phpinfo function and will show all of the configuration options for our PHP instance.

Now lets start up the built in PHP server and take a look at it:

> php -S test.php
php -S localhost:8000 test.php
PHP 7.0.12 Development Server started at Wed Apr 19 16:40:00 2017
Listening on http://localhost:8000
Document root is /path/to/script
Press Ctrl-C to quit.

You can then view http://localhost:8000 in your browser and see the normal phpinfo output.

One thing to note, if there's an index.php file in your directory and you don't specify the second parameter there it will use the index.php as the default script.

Easy, right? Now that we know how to use the built-in server we can move on to the installation of the Slim Framework and using it for our API.

Installing the Slim Framework

Since this part of the series is all about setting up the initial parts of the application, we're starting with the base: the Slim Framework. Thanks to Composer, pulling it into our application is simple with a basic require command:

> composer require slim/slim

Using version ^3.8 for slim/slim
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
[...]
Writing lock file
Generating autoload files

If you're not already familiar with the Composer package manager, check out their Getting Started guide for more information.

This will install the basic Slim framework and some of its related components like the nikic/fast-route component for request routing and the pimple/pimple dependency injection library. The Slim framework makes heavy use of the Pimple container (from SensionLabs, creators of Symfony) to handle dependencies in your application. Most of the request and response handling centers around this container. This will make more sense as we go along and you see the example code.

If all goes well with the Composer installation it should generate the autoload files and create a composer.lock file with details about the version of Slim you installed. If there were errors you'll need to debug those before moving on - you definitely need to have Slim and its dependencies installed before trying the following code.

Creating our First Route

With Slim all installed we can then just use it directly in our scripts thanks to the autoloading (PSR-0 or PSR-4) that Composer sets up for us. Slim makes it super simple to create routes, defining them right in our one file - no directories or extra files needed. This has both advantages and disadvantages, however. It's nice to be able to keep things all in one place but when things start getting complex it can make maintenance more difficult.

We're going to start small, though, so let's create our first route. First make an index.php file and put this in it:

require_once 'vendor/autoload.php';

$app = new \Slim\App();
$app->get('/', function() {
    echo 'It works!';
});
$app->run();

Now if we fire up the PHP built-in server and load it in the browser you should see the "It works!" message at the root. In the code above we've defined a GET route on the root / path. Things get a little more complex when we start adding in other route types but the Slim manual has lots of information on that so I won't rehash it all here.

Now that we've set up a basic application we can move on to changing a few things about how it works to make it suit our API needs.

Working with the Configuration

One of the main features about the Slim framework is its use of a dependency injection container to define configuration values and other resources you'll be using in your application. This also includes some special settings that Slim uses internally to do things like override default error handling. It also works as a repository for our controllers, making it easier to use them directly in our routes.

I'm getting ahead of myself on things though. Let's start with those custom settings that will help us in our Slim API. These exact configurations are specific to Slim's handling but the ideas are the same and could be re-implemented in just about any framework out there (some might even do it automagically for you).

notFoundHandler

This handler is used by Slim when a URL that's called isn't found in the listing of defined routes. With this error handler, we can set up an API friendly response of the end user calls something incorrect. Here's an example:

$app = new \Slim\App();
$container = $app->getContains();

$container['notFoundHandler'] = function($container) {
    return function ($request, $response) use ($container) {
        return $container['response']
            ->withStatus(404)
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode(['error' => 'Resource not valid']));
    };
};

It may look a little convoluted if you're not used to the PSR-7 structure, but it's pretty simple when you break it down. Essentially, if a URL is called that doesn't exist, we'll return a response object with a HTTP status of 404 and a JSON message with the error value of "Resource not valid":

{
    error: "Resource not valid"
}

Slim then uses this handler internally when the endpoint isn't defined - no other work is needed.

notAllowedHandler

Another one of the special settings that Slim makes use of is the notAllowedHandler. This handler is used when when method used to call an endpoint isn't allowed, like if they call a POST-only endpoint with GET:

$container['notAllowedHandler'] = function($container) {
    return function ($request, $response) use ($container) {
        return $container['response']
            ->withStatus(401)
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode(['error' => 'Method not allowed']));
    };
};

It's pretty similar to our notFoundHandler above, just with different content in the response: a 401 and the error message of "Method not allowed".

errorHandler

The final special option I want to cover here is the use of the errorHandler. In the application structure I've chosen, we're going to make use of PHP's exceptions to stop execution when there's an error and report back to the user. This prevents us from having to do any trickery with outputting JSON directly inside of our controllers and returning at odd places. It also conforms to the "fail fast" mentality, returning immediately if it finds something wrong.

The errorHandler is a bit more verbose than the previous examples, but bear with me. First the code:

$container['errorHandler'] = function($container) {
    return function ($request, $response, $exception = null) use ($container) {
        $code = 500;
        $message = 'There was an error';

        if ($exception !== null) {
            $code = $exception->getCode();
            $message = $exception->getMessage();
        }

        // Use this for debugging purposes
        /*error_log($exception->getMessage().' in '.$exception->getFile().' - ('
            .$exception->getLine().', '.get_class($exception).')');*/

        return $container['response']
            ->withStatus($code)
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode([
                'success' => false,
                'error' => $message
            ]));
    };
};

A lot of this handler should look familiar to you by now with the same function signatures and return of the same response object. There's a few differences though, mostly around the handling of exceptions. You'll notice that there's a new parameter called $exception in the inner closure. That's passed in to the error handler by Slim's internal functionality so we can evaluate the exception thrown. In our case we're setting a default $code and $message in case the exception isn't set (not all errors are exceptions) and then, if the exception is set, updating those values. In our setup we'll be using the exception's "code" value as our returned HTTP status code. This gives us control inside of the controllers as to what's returned back to the end user and eliminates the overhead of trying to figure out which code to use.

The response object is then returned using this code, the message provided in the exception and a success value of false. This return structure using the success value will be consistent in our API and will be returned along with every response (save error responses). In our controllers we can then just throw exceptions and this errorHandler will correctly deal with them:

$app = new \Slim\App();
$app->get('/', function() {
    throw new \Exception("You shouldn't have done that!", 418);
});
$app->run();

This will result in the response:

{
    success: false,
    error: "You shouldn't have done that!"
}

The HTTP code on the response will be that 418 specified in the throw of the exception inside the route. Eventually you could also use custom exceptions/throwables by defining them in classes and making them a bit more reusable (like CouldNotSaveRecord or InvalidInput exceptions).

Enjoying the article? Consider contributing to help spread the message!

One Last Thing

I wanted to make one last update before we move on to the next part in the series. In our current setup the base route of / responds with some plain text "It works". This is isn't very API friendly and, now that our other error handlers are API enabled, lets change this too. Slim's request/response handling makes this an easy change. We just return a new Response instance with a similar setup as the output from the error handlers:

$app->get('/', function() {
    return $response->withHeader('Content-Type', 'application/json')
        ->write(json_encode(['message' => 'It works!']));
});

Now when we hit the / route, the response will come back with an application/json content type and the JSON:

{
    message: "It works!"
}

Setup Complete...For Now

In this article I've walked you through some of the basics of setting up the Slim framework, creating an application and configuring a few handlers to help make things simpler down the road. There's other configuration options we'll come across in the future but this gets you started.

In order to help make it easier to follow along with this series and the code created each time, I'll be adding a branch for each part on a GitHub repository. The code in the branch will be the end result of each article so that, hopefully by the end, we'll have a complete API example you can use as a guide in the future.

The repository is located here: https://github.com/psecio/secure-api.

So, go on over and clone it and try it out with some sample requests and the examples we've covered here.

Resources

by Chris Cornutt

With over 12 years of experience in development and a focus on application security Chris is on a quest to bring his knowledge to the masses, making application security accessible to everyone. He also is an avodcate for security in the PHP community and provides application security training and consulting services.

Enjoying the article? Consider contributing to help spread the message!