7

I'm writing a REST API with Slim. I have written a small middleware to protect the resources so only authenticated users will be able to access them:

<?php
class SecurityMiddleware extends \Slim\Middleware
{
    protected $resource;
    public function __construct($resource)
    {
        $this->resource = $resource;
    }
    public function call()
    {
        //get a reference to application
        $app = $this->app;
        //skip routes that are exceptionally allowed without an access token:
        $publicRoutes = ["/","/login","/about"];
        if (in_array($app->request()->getPathInfo(),publicRoutes)){
            $this->next->call(); //let go
        } else {
            //Validate:
            if ($this->resource->isValid()){
                $this->next->call(); //validation passed, let go
            } else {
                $app->response->setStatus('403'); //validation failed
                $app->response->body(json_encode(array("Error"=>"Access token problem")));
                return;
            }
        }
    }
}

This works, but the undesired side effect is the middleware does not make a distinction between existing routes and non-existing routes. For example, if a the user attempts to request a route like /dfghdfgh which does not exist, instead of getting an HTTP status code of 404 he'll get a 403 saying there is no access token. I would like to add an implementation similar to the following check on the middleware class:

if ($app->hasRoute($app->request->getPathInfo()){
    $this->next->call(); //let go so user gets 404 from the app.
}

Any ideas how this can be achieved?

snollygolly
  • 1,858
  • 2
  • 17
  • 31
user1555863
  • 2,567
  • 6
  • 35
  • 50

3 Answers3

6

I use a hook to do what you're trying to do, as MamaWalter suggested, but you want to use slim.before.dispatch rather than an earlier hook. If the route your user is trying to visit doesn't exist, the hook will never be called and the 404 gets thrown.

I'm doing exactly that in my own Authorization Middleware. Works like a charm.

Jeremy Kendall
  • 2,869
  • 17
  • 17
  • i just realized that you wrote the tutorial that i followed and my example is based on your work :) Nice work, really helpful ... sorry i should mention it. – MamaWalter Feb 21 '14 at 14:34
  • That's awesome! I thought it looked familiar. I figured it was just a case of great minds thinking alike :-) Looks like I need to update the method's docblock to change the recommended hook. – Jeremy Kendall Feb 21 '14 at 17:27
  • 1
    Is it possible to hook only some routes to trigger middleware call()? – Bagusflyer May 17 '14 at 15:35
3

Maybe my implementation will work for you:

<?php

class CustomAuth extends \Slim\Middleware {

    public function hasRoute() {
        $dispatched = false;

        // copied from Slim::call():1312
        $matchedRoutes = $this->app->router->getMatchedRoutes($this->app->request->getMethod(), $this->app->request->getResourceUri());
        foreach ($matchedRoutes as $route) {
            try {
                $this->app->applyHook('slim.before.dispatch');
                $dispatched = $route->dispatch();
                $this->app->applyHook('slim.after.dispatch');
                if ($dispatched) {
                    break;
                }
            } catch (\Slim\Exception\Pass $e) {
                continue;
            }
        }

        return $dispatched;
    }

    public function call() {

        if ($this->hasRoute()) {
            if ($authorized) {
                $this->next->call();
            }
            else {
                $this->permissionDenied();
            }
        }
        else {
            $this->next->call();
        }
    }
}
gorodezkiy
  • 3,299
  • 2
  • 34
  • 42
2

Not exactly what you asking for, but personnaly when i need to check authentification on some routes i do it like this.

config:

$config = array(
    ...,

    'user.secured.urls' => array(
        array('path' => '/user'),
        array('path' => '/user/'),
        array('path' => '/user/.+'),
        array('path' => '/api/user/.+')
    ),
    ...

);

middleware:

/**
 * Uses 'slim.before.router' to check for authentication when visitor attempts
 * to access a secured URI.   
 */
public function call()
{
    $app = $this->app;
    $req = $app->request();
    $auth = $this->auth;
    $config = $this->config;

    $checkAuth = function () use ($app, $auth, $req, $config) {

        // User restriction
        $userSecuredUrls = isset($config['user.secured.urls']) ? $config['user.secured.urls'] : array();
        foreach ($userSecuredUrls as $url) {
            $urlPattern = '@^' . $url['path'] . '$@';
            if (preg_match($urlPattern, $req->getPathInfo()) === 1 && $auth->hasIdentity() === false) {

            $errorData = array('status' => 401,'error' => 'Permission Denied');
            $app->render('error.php', $errorData, 401);
            $app->stop();                   
        }
    }

    };

    $app->hook('slim.before.router', $checkAuth);

    $this->next->call();
}

but if almost all your routes need authentification maybe not the best solution.

great example: http://www.slideshare.net/jeremykendall/keeping-it-small-slim-php

MamaWalter
  • 2,073
  • 1
  • 18
  • 27
  • Thanks for the answer. So basically you're just doing the opposite: Instead of letting go of publicly available routes, you're just catching the ones that need authentication. In my case, almost all of the resources require authentication, So I don't think this approach fits my use-case. – user1555863 Feb 20 '14 at 08:57