7

I have set-up a MVC/PAC-like structure for a web application (unsure if it fits any of these design patterns fully). In short it is:

  • Routing in index.php, which selects the controller and method using the URL http://example.com/controller/method/<params>
  • Controller's method requests data from 'model(s)' and assigns it to a view.

Now I am wondering what is the best spot for an logged in check. Let's say I have a page at http://example.com/controller-one/method-one/ which requires the user to be a logged in administrator; Where do I check whether the user actually is? In the routing, controller or model?

Please note that a controller and/or model might contain methods with different 'rights'.

Note: There is a model called Authentication which contains a method called isLogged() that returns true or false based on whether the user is logged in and another method which is called IsLoggedAdmin() which returns true or false based on whether the user is a logged in administrator.

So...: What is the best location to call the method isLogged or isLoggedAdmin. In the controller's contruct and/or method(s) or the model's contruct and/or method(s)?

  • 1
    Dunno for PHP but in Rails this call goes into the controller for the reasons you mention (normally in something called the before_filter). The 'technical' implementation of finding the user and checking permissions goes into the user model. – thorsten müller May 3 '16 at 8:47
  • @thorstenmüller Ah yes, I forgot to mention that the log-in method + the checks are in a authentication (user) model. But... if a 'admin' method of a model would be called by two different controller's does this mean implementing the same check for two controllers? – kgongonowdoe May 3 '16 at 8:56
  • 1
    In Rails you would have a class ApplicationController from which the other controllers are derived. Or similar top level controllers. Say if you have a public part without checks (derived from ApplicationsController) and a shop that needs login (derived from say ShopController). Als AdminController is very common for the admin interface. So the parent controllers hold all necessary code and the single controllers just 'activate' it like before_filter :authenticate_user, only: [:new, :create, :edit, :update, :destroy] which would require authentication for those methods in the controller. – thorsten müller May 3 '16 at 9:15
  • It belongs to the place where a redirect may happen. In traditional MVC architecture that will always be controllers. – Andy May 4 '16 at 10:28
  • I prefer to do this stuff in chained (single method) controllers (UseCase/Action). See how "middlewares" in popular frameworks are implemented or read about Chain of responsibility pattern (if you like to keep frameworks on distance). – shudder May 9 '16 at 17:51
3

Authentication should happen after routing but before calling controller or its methods.

At that point you know which route was requested and can check if user has privileges to perform a certain action (call controllers).

This allows not only to separate concerns, but also to decide how to handle unathorized requests before they hit controllers - eg. redirect them to other controller internally with 403 response.

Authentication/authorisation happens in higher layer than models. If they depend on user instances - you pass already authenticated/authorized instance. Also - when requirements change, and other roles are allowed to call previously prohibited methods - you change only ACLs and not models.

Symfony framework had a nice description how they did, it's still available for v.2: http://symfony.com/doc/2.0/book/security.html

2

I'm from the Java World, but things are pretty similar, so here are some hints about rights checking in my opinion.

If your right is a right like "have the right to perform action X". Then you should check before routing and in the service layer.

  • The check in the service layer is to be sure that whenever you call that service, you'll have a said check.
  • The really early check is in order to not have quite some of your code (data fetching,...) that can happen before checking the right. I was told it was done too to sustain more efficiently DDOS attacks. The earlier you reject a request, the better.

If you have a right like "have the right to perform action X on Y" then i guess you can only have it in the service layer, as the earlier instruction possible. However if your framework provide facilities to perform that when performing the routing, go for this, however i frankly prefer to have the service check, as the service's function can be used from various places.

You could perfectly have two sets of Rôles :

  1. One like : ADMIN/GUEST/USER : used for fast routing check, stored in user session
  2. Rights : used by service layer, stored in database or any persistent storage.
  • Before routing, but you mean before instantiating a Controller. Checking user stuff after route determination seems to be key, as one must know a route exists before testing authentication and authorization. – Anthony Rutledge Aug 29 '18 at 0:47
  • When I hear Java people say things like "Service Layer" in a conversation about MVC, it feels like they do not know where the "Service Layer" would reside in an MVC architecture (even if that is not the case). I think the key is to envision the Model as having layers, instead of looking at the Model as a monolithic thing. – Anthony Rutledge May 8 at 12:38
  • 1
    In a classic "Anemic way" of doing thing, the Model contains the Service Layer, the data model,the persistence management, and also the DTOs used for communications and even some more stuff. You could say it contains everything except view and controller, but that still can make it quite big in moderate applications that include authentication, authorization, multiple data sources, multiple interconnection with others systems and so on (be carefull here : when interconnecting with others system you also have potentially controllers and dedicated views of your data between the systems). – Walfrat May 9 at 9:50
0

An attempt to simplify and clarify.

Step 1: Determine if the route exists. If the route does not exist, remember to send a 404 (Not Found) HTTP response code in addition to anything else you do.

Step 2: If the route does exists, determine if it requires authentication (identity establishment).

Step 3: If authentication is required, check the authentication status (is user logged in?), before instantiating a Controller. Note, this means that a route should be defined by the combination of an HTTP request method, the name of the Controller, the action / command, and a Boolean flag that indicates the route is either public or private (secure).

Without these four things being pre-staged and available to the Router, you would waste compute resources in the Controller, or worse the Model, attempting to resolve authentication status.

The key is to have abstract classes for Controller, PublicController, and PrivateController, and then extend the latter two accordingly. However, only instantiate children of PrivateController when authentication matters have been satisfied. This is vital if you are using constructor injection, as a Model and View must be selected for the PrivateControllers to manange.

There is no point in instantiating children of PrivateController (along with their dependencies) if the HTTP request method, route (controller/action), and authentication have not been resolved.

Step 4a: If the user/visitor is not logged in, redirect to LoginController (a child of PublicController) with a 303 (See Other) HTTP response code. If login fails, remember to send a 401 (Unauthorized) HTTP response code.

Step 4b: If the user has already authenticated (logged in), check if he/she/it has the rights required to access the resource and action/method in question (before instantiating the PrivateController child). If he/she/it has insufficient rights, remember to send a 402 (Forbidden) HTTP response code.

You determine where someone ends up after logging in / an error / failure to authenticate for access to protected content.

Step 5: Now, after the Controller has been instantiated, make sure the user is still logged in and verify authorization (rights) to take a specific action.

Summary

In effect, the first authentication and authorization check is about the nature of the HTTP request itself. Should the request be allowed to make the system do lots of work to get to the action / method being requested?

The second authentication and authorization check is about making sure the state of the request is still valid across a time domain (although the time window may be very small, indeed). Accounts get suspended, demoted, and force logged out all of the time. Just because the initial request is good does not mean it is still good .005 seconds later.

Implementation

One could use a class like this to help a Router to decide if it should attempt to instantiate a child of PublicController straight away, or deal with authentication matters first.

/**
 * Class that holds HTTP request method / Controller / action / secure relationships.
 *
 * @version 4.0
 * @author Anthony E. Rutledge
 * @link https://www.linkedin.com/in/anthony-rutledge-2988b0125/
 */
class MvcRouteMap
{
    private $routes = [
        'GET' => [
            'Root' => [
                'index' => ['secure' => false]
            ],
            'Contact' => [
                'index' => ['secure' => false]
            ],
            'Error' => [
                'index' => ['secure' => false]
            ],
            'Login' => [
                'index' => ['secure' => false]
            ],
            'Newsletter' => [
                'index' => ['secure' => false]
            ]
        ],
        'POST' => [
            'Contact' => [
                'send' => ['secure' => false]
            ],
            'Login' => [
                'login' => ['secure' => false]
            ],
            'Newsletter' => [
                'signup' => ['secure' => false]
            ]
        ],
        'PUT'  => [

        ],
        'PATCH' => [

        ],
        'DELETE' => [

        ]
    ];

    public function __construct()
    {

    }

    public function isSecureRoute(string $httpRequstMethod, string $controller, string $action): bool
    {
        if ($this->isRoute($httpRequstMethod, $controller, $action)) {
            return $this->routes[$httpRequstMethod][$controller][$action]['secure'];
        }

        throw new RuntimeException("The resource requested does not exist.");
    }

    private function isRoute(string $httpRequstMethod, string $controller, string $action): bool
    {
        return isset($this->routes[$httpRequstMethod][$controller][$action]);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.