PhpRiot
News Archive
PhpRiot Newsletter
Your Email Address:

More information

The Danger of Hooks

Note: This article was originally published at Planet PHP on 11 January 2012.
Planet PHP
I fell in love with Hooks in frameworks recently; the honeymoon period was tragically short.

First, the love story:

I ran into hooks rather simultaneously with two very different frameworks: Code Igniter and Lithium. In both cases I was using a rather nifty hook to handle ensuring that users were properly authenticated and authorized before accessing a page. I think we can all agree having to add some code to every single method is foolhardy: if (!isset($_SESSION['user_level']) OR ($_SESSION['user_level'] != 'admin')) { header('Location: ' . APP_ROOT_URI); exit; }

Hooks provide a great way to solve the issue. Within Lithium you can leverage the filters mechanism to handle authentication quite easily, in fact it's an example case in their tutorials. The beautiful thing about it in my mind is the simplicity of things at the controller level: I simply list the publicly accessible methods in a property (public $publicActions = array('login');), and everything else is assumed to only be accessible to logged in users. Fantastic! If I mess up when adding a new method, it defaults to closed, which is exactly the result I'm looking for.

Over with Code Igniter things are really quite similar. The post_controller_constructor hook can be used to invoke a specific class and controller. That class can return true, redirect a user, whatever as appropriate. Since it's invoked after the controller is instantiated, similar configuration options can be made available.

The honeymoon

I ripped redundant, error prone, easy to forget, and fundamentally stupid checks out of all of the controllers where I'd added them. These new systems were much easier to maintain, required a lot less code, and I didn't need to add 10 lines of unrelated bloat into each controller. Life was grand. Days where I wrote negative lines of code felt glorious.

The big fight

One day, while messing around, I accidentally turned off the hook configuration within Code Igniter (actually I clobbered a file, and restored the wrong one). Then, things came crashing down in a horrible cacophony of... actually they didn't. Everything kept working: that was the problem. The entirety of my security system was turned off because one file was wrong, and things kept working. Sure, specific calls that referenced the current user's username broke, but there was more than enough left vulnerable for me to get a big chill.

Counselling

Revisiting the hooks system, I was shocked by the tremendous lack of depth in my defense: one mis-configured file and security was off. Even worse, nothing broke. There was no evidence that the security was disabled unless you went probing, which is horrible. The only thing worse than a safe that won't lock, is one that looks like it's locked but pulls open to a slight touch.

Conciliation

The easy thing to reach for with both Lithium, and Code Igniter is __construct(). A single, unified location to ensure that the authorization tests have been executed. Unfortunately, in both cases __construct() is called long before the authorization hooks are run. More specialized solutions are required.

With Lithium

The __invoke() method is invoked after the authorization filter, so it's a great candidate for double checking. public function __invoke($request, $dispatchParams, array $options = array()) { if (!defined('AUTH_CHECKED')) { throw new DispatchException('Authorization filter not run.'); } return parent::__invoke($request, $dispatchParams, $options); }

With Code Igniter

The _remap() function is called (when available) to allow you to remap incoming requests to a different method. Since it's invoked universally, the check can go there. public function _remap($method) { if(defined('AUTH_CHECKED')) { $this-$method(); }else { exit('ACL Configuration Error'); } }

Both of these cases provided me with the depth I was looking for. I'm no longer entirely dependent on one configuration option or file for my security to function. Should it fail, I've got a secondary check in place; this example of defence in depth allows me to be comfortable with the hooks security system once more.

Final thoughts

Through researching this, and exploring several code bases to which I have access, I've noticed two distinct strategies for managing method level access. In Lithium, each class indicates which methods should be accessible to which security groups using a public property; by contrast, in the other strategy, public methods were explicitly laid out inside the single authorization function. The former has the advantage of allowing you to quickly and easily manage security while you add functions to a given controller; the latter solves the issue of your authorization rules being scattered across each and every controller by centralizing them in one easy to locate file.