I have a custom MVC framework that is in a constant state of evolution. There’s a long standing debate with a co-worker how the routing should work. Considering the following directory structure:
/core/Router.php
/mvc/Controllers/{Public controllers}
/mvc/Controllers/Private/{Controllers requiring valid user}
/mvc/Controllers/CMS/{Controllers requiring valid user and specific roles}
The question is:
“Where should the current User’s authentication be established: in the Router, when choosing which controller/directory to load, or in each Controller?”
My argument is that when authenticating in the Router, an Error Controller is created instead of the requested Controller, informing you of your mishap; And the directory structure clearly indicates the authentication required.
His argument is that a router should do routing and only routing. Leave it to the Controller to handle it on a case by case basis. This is more modular and allows more flexibility should changes need to be made by the router.
https://codereview.stackexchange.com/questions/26345/php-mvc-custom-routing-mechanism alluded to it, but the topic was of a different nature.
Alternative suggestions would be welcomed as well.
3
I’d have another component in between that determines the access rights.
This component can have a set of mappings of user roles to resources, where controllers and/or actions are the resources.
The execution would then go like this:
Router -> Authorization -> Controller
Router: determines which controller and action to execute
Auth: find out which user role is logged in and check
if the user has access to the controller/action,
if not, throw an exception that will get caught by the error handler,
or redirect to login page for guests, or whatever
Controller: if the authorization component didn't complain, do work as usual
If you want to get fancy, you can use php doc comments and reflection to annotate your methods and controllers on the spot instead of having a big list of allowed resources for user roles. Something like:
class Controller {
/**
* @allow({'admin','user'})
* @prevent({'guest'})
*/
public function someAction(){}
}
The auth component can then read the annotations to determine access rights, instead of having a big list of role->resource mappings
1
Here is an idea. Technically, maybe both of you are not correct. Maybe the best place for the user’s login status and role to be validated in is a concrete instance of a Validator
class.
This begs the question, what would a ControllerValidator
do in your scenario beyond checking to see if a user is logged in? It could test five fundamental things about an HTTP request to derive if it is a reasonably good request, or probably a malicious hack.
1) Minimum inputs (Were enough HTML controls submitted?)
2) Maximum inputs (Were too many HTML controls submitted?)
3) Correct inputs (Were the HTML controls submitted the right ones?)
4) Correct encoding (Typically, is the encoding UTF-8. But, is it what you want / need?). This check should have its own specialty validator class. Inject an instance of this object into all ControllerValidator
objects.
5) Max input sizes (Is any input beyond the acceptable size that should have made it this far?).
So, the answer is that controllers should be checking user and role stuff (because it is relevant to the HTTP request, which is the carrier of the ultimate command). Remember, the definition of an HTTP URL allows for user authentication. So, checking the user as part of the request is a legitimate task, even if you are not using HTTP authentication. Role verification is simply an extension of user authentication.
Now, this UML diagram is just an abstract. However, it does capture the essence of checking the intent of an HTTP request (good faith request, or clearly malicious). Either all the tests of a ControllerValidator
pass, or the request is rejected and terminated.
Notice though, going by your requirements,that there would be a difference between a PrivateControllerValidator
and a CMSControllerValidator
. The former would not be allowed to do role checking. Is that how you really want it to be? It seems to me that a CMSControllerValidator
is a PrivateControllerValidator
. Are you certain that a PrivateControllerValidator
instance will never need to utilize roll checking?
Update: If you have a URL rewriting web server and a Router
class, you would only need to do the five tests above for things related to the HTTP request headers, initially. Once you have tested enough to send data to the Router
, you can do page specific validations in the model (in PHP, this would include validating $_POST, $_FILES, $_GET, $_COOKIE, or whatever).
I would go with your colleagues suggestion of leaving the Router to do its duty and not take others responsibility. I think its a good programming practice and would help others to understand the same as well.
Now, for the controllers, suggest you go ahead and use inheritance. Have a parent controller, Base_Controller, which can do all the chores which is common across all controllers.
From Base, derive Secure_Controller and NonSecure_Controller, which does what the name states.
You can always throw an exception from the controller, which can well be handled by hooks to display the view gracefully.