Quickly Build RESTful APIs in PHP with Slim – Part 4

The fourth and final part of this series is focusing on using the Slim Framework’s middleware functionality to validate (to a degree) RESTful API requests. This post will show how to add a simple API key to the requests. There is obviously more one could do, like having a secret key (which would not be passed to the server) to encode some data to further validate the user, but a simple API key is a good first step.

Now, the word middleware may conjure up a lot of Dilbert-type images, but Slim’s middleware is simple and highly functional. To start, let’s modify the route:

   // The old route
   // $this->app->get('/:id', array($this, 'getItem'));
   $this->app->get('/:id/:key', array($this,'verifyKey'), array($this, 'getItem'));

As you can see, there are two differences. The first is that there is now a :key parameter. The second is an array containing a reference to $this and the name of our key verification call, verifyKey. The reason that an array must be passed in, instead of just listing the name of the function, is that this is part of a namespaced PHP Class.

Next, there needs to be a table holding keys. A very basic example:

CREATE TABLE IF NOT EXISTS `apikeys` (
  `apiKey` varchar(32) NOT NULL,
  PRIMARY KEY (`apiKey`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `apiKeys` (`apiKey`) VALUES
('12345'),
('abcde');

One word of caution: you may be tempted to call the table keys and the field key. Those are reserved MySQL words, so to save future headaches, you will probably want to avoid that.

Now, let’s take a look at the verifyKey function. It takes the key that was passed in via the URL and does a quick database lookup to see if it exists. If it doesn’t exist, it returns an error message, and $this->app->stop() is called to stop any further processing. If the key does exist, nothing is done (currently), and the getItem function is subsequently called.

    public function verifyKey(\Slim\Route $route) {
        try { 
            $key = $route->getParam('key');
            $sql = "select * from apiKeys where apiKey = :apiKey";
            $s = $this->dbh->prepare($sql);
            $s->bindParam("apiKey", $key);
            $s->execute();
            
            $keyVerification = $s->fetch(\PDO::FETCH_OBJ);
            if($keyVerification) {
                // nothing to do currently
                // record number of API requests?
            } else {
                // no key found, thus this key is invalid
                $this->app->status(400);
                $result = array("status" => "error", "message" => "You need a valid API key.");
                echo json_encode($result);
                $this->app->stop();
            }
        } catch(\PDOException $e) {
            $this->app->status(500);
            $result = array("status" => "error", "message" => 'Exception: ' . $e->getMessage());
            echo json_encode($result);
            $this->app->stop();
        }
    }

One bit of code I’ll point out:

$this->app->status(400);

This sets the HTTP status code. There’s plenty of discussion on the web on the correct HTTP status code to respond with. See this StackOverflow question on the topic. Regardless, 400 means "Bad Request", which a request needing a proper API key undoubtedly is. When there is a database issue, that would qualify as an internal server error, which is HTTP status code 500.

The reason these codes are important is they let the system or user making these calls know if there is a problem with their request or a problem with the server. A well designed system would not continue to make requests depending on what codes it received.

The nice thing about this middleware functionality is that no changes were made to the original getItem function:

    public function getItem($id) {
       try { 
            $sql = "select * from commodores where id = :id";
            $s = $this->dbh->prepare($sql);
            $s->bindParam("id", $id);
            $s->execute();
            $result = array("status" => "success", "commodore" => $commodore);
            echo json_encode($result);
       } catch(\PDOException $e) {
           echo 'Exception: ' . $e->getMessage();
       }
    }

It has no idea that a key was passed in, let alone that the key was validated against another table, and that is ideal. Some example calls:

URL: http://local.api.ericbrandel.com/v1/get/6/notGonnaWork

{
   "status":"error",
   "message":"You need a valid API key."
}

URL: http://local.api.ericbrandel.com/v1/get/6/12345

{
   "status":"success",
   "commodore":
      {
         "id":"6",
         "name":"Commodore 64",
         "url":"http:\/\/en.wikipedia.org\/wiki\/Commodore_64"
      }
}
This entry was posted in PHP, Web Development and tagged , , , , , , , . Bookmark the permalink.

6 Responses to Quickly Build RESTful APIs in PHP with Slim – Part 4

  1. Dave says:

    Hi Eric,

    What perfect timing! I had considered adding an api key to my slim/redbeanphp app but kept putting it on the back burner. Your article just lit the fire to get me going again!

    Thanks!

    Dave

  2. Francisco Mancardi says:

    Great work! this series of posts is the documentation I was looking for.
    Thanks a lot!

  3. gis_gps says:

    thanks a lot!

  4. Immanuel says:

    Thanks, this has been a real help!

  5. Swapnil says:

    Thanks! I was looking for such easy to understand but detailed documentation.
    There are lots of typos on slim documentation, which frustrated me initially. I’ve got a clear picture of code arrangement, power of middleware in Slim and simple syntax of Slim.

    Many thanks!

  6. Swapnil says:

    Thanks! I was looking for such easy to understand but detailed documentation.
    There are lots of typos on slim documentation, which frustrated me initially. I’ve got a clear picture of code arrangement, power of middleware in Slim and simple syntax of Slim in this series. You increased my confidence with Slim!

    Many thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *