Implementing Login-Middleware on a grouped route


#1

Hey, I learned PHP last week and have been playing with Slim Framework since then to create a small webapp for a university project I run internship for. (my app is about realtime monitoring and reservations of seats in a library)

Everything has been going great and I’m really liking Slim. I only have this small thing I can’t get around. Login happens with CAS, using phpCAS, so I got myself this as middleware, as it stands it currently requires everyone to log in when visiting the site. Now my mentor asked me if it’s possible to only require a login when people want to make reservations and let everybody view the realtime index page. I have made a group for /reservations, but I have been struggling to apply the middleware to only that route. The actual login happens at phpCAS::forceAuthentication(); but I when I move it to the /reservationsgroup with the container setters, the login page shows when trying to access reservations as expected, but the container filling is where I fail. On every other place in the app I fail to retrieve the user’s name and attributes from the container.

So does anybody know where I can find documentation about middleware on specific groups or give me a hint on this?

Thanks a lot!

middleware.php:

<?php
// Application middleware
// e.g: $app->add(new \Slim\Csrf\Guard);
$app->add(function ($request, $response, $next) use ($app) {
    $container = $app->getContainer();
    $settings = $container->get('settings');
    $cas_auth = $settings['cas_auth'];

    phpCAS::setDebug('/tmp/phpCAS.log');
    phpCAS::client(SAML_VERSION_1_1, $cas_auth['sso_host'], $cas_auth['sso_port'], $cas_auth['sso_uri'], true, 'saml');
    phpCAS::handleLogoutRequests(true, $cas_auth['sso_logout_server']);
    if ($cas_auth['ca_certificates_path']) {
        phpCAS::setCasServerCACert($cas_auth['ca_certificates_path']);
    } else {
        phpCAS::setNoCasServerValidation();
    }

    //LOGIN HAPPENS HERE
    phpCAS::forceAuthentication();
    
    $container['user'] = function ($container) {
        return phpCAS::getUser();
    };
    $container->view->offsetSet('user', phpCAS::getUser());
    $attr = phpCAS::getAttributes();
    $response = $next($request, $response);
    return $response;
});

My group:

$app->group('/reservaties', function () use ($app) { 
   $app->get('', function ($request, $response) {
      //Here I tried to force phpCAS::forceAuthentication()
      ...
   }
   $app->post('/{id}', function ($request, $response, $container) use ($app) {
      ... 
   }
...
}

#2

Personally I like to keep my Middlewares ( as well as my controllers ) in a separate directory and then apply them either globally, group or per route.

Application wide middleware

use Middleware/LoginMiddleware
.
.  
.
$app->add(new LoginMiddleware($container['login_settings']);

Group middleware

$app->group('/programs', function () {
    $this->get('/areas/', 'Controller\ProgramController:getAreas');
    $this->get('/degrees/', 'Controller\ProgramController:getDegrees');
})->add(new LoginMiddleware($container['login_settings']);

Route middleware

$app->get('/areas/', 'Controller\AreaController:getAll')->add(new LoginMiddleware($container['login_settings']);

I’d try separating your middleware into its own file and then apply it your empty ‘’ route

$app->group('/reservaties', function () use ($app) { 
   $app->get('', function ($request, $response) {
      //Here I tried to force phpCAS::forceAuthentication()
      ...
   }->add(new /Middleware/LoginMiddleware($app->getContainer()));
   $app->post('/{id}', function ($request, $response, $container) use ($app) {
      ... 
   }
...
}

Check out the various ways to apply middleware here.


#3

Hey,
Thanks for the reply. After long trial and error I managed to make it work. My mentor suggested applying the middleware to all routes which was easy, and then to check in the middleware itself which route I was on, and apply logic.
With a regex I did this:

  #LOGIN HAPPENS HERE
  if(preg_match('/^\/reservaties/', $request->getUri()->getPath()) == 1) {
    phpCAS::forceAuthentication();
    $_SESSION['_user'] = phpCAS::getUser();
  }

And since how middleware works I did not have to explicitly do ->add new ... This worked perfectly for me. Came back with the answer to help out people also dealing with login problems like this.
Kind Regards Bono


#4

If you use groups or named routes you could also take the following approach, if you find yourself locking down more then one route or groups.

###Grouped Routes
.
.
$route = $request->getAttribute(‘route’);
$groupRoute = $route->getGroups()->group;
if($groupRoute === ‘/reservations’) {
phpCAS::forceAuthentication();
$_SESSION[’_user’] = phpCAS::getUser();
}
.
.

Named Routes

.
.
    $route = $request->getAttribute('route');
    $namedRoute = $route->getGroups()->name;
    $authRoutes = ['reservations', 'admin'];

    if(in_array($namedRoute, $authRoutes)) {
        phpCAS::forceAuthentication();
        $_SESSION['_user'] = phpCAS::getUser();
    }
.
.

You could combine them together in your own custom middleware. I’ve something similar with a default deny policy. I use named routes and also groups to state which routes/groups are public. This is the configuration I use.

$c['auth'] = [
    'loginRoute' => 'login',
    'publicRoutes' => [
        'login',
        'course-tags',
        'registration'
    ],
    'publicGroups' => [
        '/degrees',
        '/programs',
        '/program',
        '/tags',
        '/blog'
    ]
];

loginRoute is the named route for the login page, this is where the middleware will send you if you are not logged in or authenticated. If you land on any publicRoutes or publicGroups there will be no authentication needed. I wanted to provide alternative means for others to check out.