[SLIM 4] Stop execution of code if X fails

Hi.

Here is what I’m trying to do:
Making an API with simple APIkeys for authentication, protection some endpoints.

What fails:
Even if the APikey check fails the code still runs (insert into database etc.)

Simplified code here:

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;

use Slim\Exception\HttpUnauthorizedException;

#logger
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config/db.php';

$app = AppFactory::create();

// Add Routing Middleware
$app->addRoutingMiddleware();

// start app
$app->get('/savesomething', function (Request $request, Response $response, array $args) {

// Code to save into database. 

    $response->getBody()->write("Succefully saved content");
    return $response;

});

//Middleware


// Check if API key is valid before giving token...
$app->add(function (Request $request, RequestHandler $handler) {

  $apikey = $request->getParam("apikey") ?? ""; //
  $sql = "SELECT * FROM users WHERE apikey = :apikey LIMIT 1";
  $response = $handler->handle($request);

  try {

      # Fetch data from DB
      $db = new DB();
      $conn = $db->connect();

      $stmt = $conn->prepare($sql);
      $stmt->bindParam(":apikey", $apikey);
      $result = $stmt->execute();
      $count = $stmt->rowCount();
      $user = $stmt->fetch(PDO::FETCH_OBJ);

      $db = null;

      // Check if this APi key actually exists
      if($count != 1) {

          $data["status"] = "error";
          $data["message"] = "Invalid API key";

          $payload = json_encode($data, JSON_PRETTY_PRINT);
          $response->withJson($payload);
          return $response->withJson($data)->withStatus(403);


      }



  } catch(PDOException $e) {

      $data["status"] = "error";
      $data["message"] = "Invalid API key";

      $payload = json_encode($data, JSON_PRETTY_PRINT);
      $response->withJson($payload);
      return $response->withJson($data)->withStatus(403);

  }

  $response->getBody();
    return $response;


});

How am i supposed to do this? I want the APikey check to run first and stop execution of the rest.

Hi! The rowCount method is for INSERT and UPDATE commands and returns the number of affected rows. You may try to use the result of the fetch method instead for a SELECT statement.

if($user) {
  // ...

Hi. Thanks for the reply.

I do have insert statements , just not in the sample code. Just to keep it simple.

When i call the endpoint: /savesomething
I should see “invalid API” (if its invalid ofcource). But even if its invalid, it still saved the data from the /savesomething functions.

Its something with the middleware function that doesnt work properly. it doesnt stop executing the rest and still saves data even though the APIkey is invalid.

The reason is, your current middleware is implemented as an outgoing middleware because you first invoke the handle method, and then it does the API-Key checks. This means, your endpoint will be executed before the middleware checks the credentials. To change this order, just need invoke the handle method only when the user / API-key is valid.

Ah okay, I think I understand.

Soi could do this:

$response = new \Slim\Psr7\Response();
$response = $response->withJson($payload)->withStatus(403);
return $response;

If invalid, to still output something to the browser? and if its valid, use the
$response = $handler->handle($request);

?

You need to check for a valid API-key first. If the key is valid, invoke the handle method. If the key is not valid, return a 401 response or throw a HttpForbiddenException.

throw new \Slim\Exception\HttpForbiddenException();

401 Unauthorized:

If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.

403 Forbidden:

The server understood the request, but is refusing to fulfill it.

From your use case, it appears that the user is not authenticated. I would return 401.

Hi Odan.

Thank you for all your replies.
I have cleaned up my code for the API check.

I used throw new \Slim\Exception\HttpForbiddenException($request, “XXXX”);
To throw the error.

I have one more question. Because this is an API, I want it to return the data in a specific format.

Would i just do it like this, or is there better way in SLIM4 to do it?

header('WWW-Authenticate: Basic realm="Server"');
header('HTTP/1.0 401 Unauthorized');
echo 'Who are you?';
exit;

My API key check middleware here , just to share the code :slight_smile:

// Check if API key is valid before running the app
$app->add(function (Request $request, RequestHandler $handler) {

    // Retrieve the API get from body (Raw json)
    // Example: {"apikey": "XXXX-XXXX-XXXX-XXXX"}
    $apikey = $request->getParam("apikey") ?? ""; // Api key provided. If none provided set it to empty.

    // Ignore endpoints in this array
    $ignoredEndpoints = array("/service/client/wordpress/createsitekey");
    $endpoint = $request->getUri()->getPath();

    // IF endpoint requested is in array, skip the API key check.
    if(in_array($endpoint, $ignoredEndpoints)) {

        $response = $handler->handle($request);
        $response->getBody();
        return $response;

    }

    // Check if the API key is empty,before connecting to the database.
    if($apikey == "") {

        throw new \Slim\Exception\HttpForbiddenException($request, "Missing API key...");

    }

    // Connect to the database
    $db = new DB();
    $conn = $db->connect();

    // Check the API key against users
    $stmt = $conn->prepare("SELECT * FROM users WHERE apikey = :apikey LIMIT 1");
    $stmt->bindParam(":apikey", $apikey);
    $result = $stmt->execute();
    $rowCount = $stmt->rowCount();
    $user = $stmt->fetch(PDO::FETCH_OBJ);

    // Close db connection, we got the data
    $db = null;

    // Check if this APi key auctually exists
    if($rowCount != 1) {

        throw new \Slim\Exception\HttpForbiddenException($request, "APi key invalid...");

    }

    // No errors was found, return the app responses
    $response = $handler->handle($request);
    $response->getBody();
    return $response;

});

I’m not sure what the question is about, but I guess you want to know how to implement BasicAuth in Slim, right?

My question is how to correctly send a 401 error.

This works fine:
throw new \Slim\Exception\HttpForbiddenException($request, “APi key invalid…”);

But I need a specific data format returned. Sometimes it’s just simple text, and something is JSON that I need to return upon failing.

To transform the Exception into a JSON response, you can add a handler to the Slim ErrorMiddleware or you implement a custom Middleware that catches this specific Exception and renders the Exception into (JSON) response.

Thank you. Will try it :slight_smile: