How to treat the logged-in user in a MVC system?

Asked

Viewed 785 times

3

I’m creating a MVC framework a few weeks (developing it and using it at the same time), I arrived at the part of creating the authentication system.

So I’d like to create a way for me to configure which routes the user can access when it’s not authenticated in the applications that are created on top of the Framework, without having to check each method whether the user is authenticated or not.

I already have the code section where I call the controller to get an idea of what I’m developing.

class Application {

   public static function RUN() {

      $request = New Request();

      $class = '\\Controller\\'.$request->class.'Controller';

      if (!empty($request->post['mvc:model'])){
         $model = '\Model\\' . array_remove($request->post, 'mvc:model') . 'Model';
         $param = New $model($request->post);
      } else if (!empty($request->lost))
         $param = $request->lost;
      else {
         $param = NULL;
      }

      try {
         $app = New $class();
         $action = $request->action;

         if (!is_null($param) && !empty($param))
            $app->$action($param);
         else 
            $app->$action();

      } catch (SystemException $e) {
         if ( strpos(Exceptions::E_FILENOTFOUND.'|'.Exceptions::E_CLASSNOTEXIST, $e->getCode()) !== FALSE){
            $app = New \Controller\FileNotFound();
            $app->file = $class;
            $app->index();
         } else {
            $app = New \Controller\ErrorController();
            $app->message = $e->getMessage();
            $app->error = $class;
            $app->index();
         }

      }


      $app->output();
   }
}

The point is that I’m not really in the mood of calling a method to verify that the user is authenticated to every method I create. Then you would need a way to set up in Application::Run() check if the user has permission to access the requested route.

There is already some standard in MVC models?

3 answers

2


I suggest implementing as a plugin or "hook".

I have been developing PHP frameworks for many years. In the penultimate version, built in 2009, I implemented user/login permissions reading within the Core of the framework because I fell into that same dilemma of yours. As I was in a hurry to see result, I built the login and authentication resource within the Core.

Later, with years of use and several cases, I realized that it would be better separate, as if it were a plugin, until why implement such feature in the Core in itself, was already hurting the concept MVC. The logic is that login/authentication belongs to Model.

In a clearer example, several times I had to build some small site where I didn’t need to login or register users. Then that native user permissions feature became completely useless.

Let’s see a practical example of how to implement a logic with plugins/Hooks?

class Core
{
    public function MainProcesses()
    {
        /**
        Aqui excecuta as rotinas comuns e nativas do framework, normalmente
        */

        /**
        Em algum ponto, vc deve decidir onde carregar os plugins/hooks
        */
        $this->MainProcessesHooks();
    }
    private function MainProcessesHooks()
    {
        /**
        Uma rotina que vai procurar por hooks registrados e configurados adequadamente para ser executado no método MainProcesses()

        Exemplo, aqui vc pode ter um plugin para verificar se um usuário está autenticado e setar suas permissões.

        A ideia aqui é fazer leitura de algum arquivo php, json, txt, enfim. Algo que seja rápido de carregar e processar. 
        Nesse arquivo, conterá informações sobre quais os hooks/plugins devem ser carregados aqui.
        */
         $p = '/path/absolute/of/app/Plugins/Core/MainProcesses.php';
         if (file_exists($p))
             include $p;
    }
}

Within "Mainprocesses.php"

<?php
include '/path/absolute/of/app/Model/Users/Plugins/Permissions.php';
include '/path/absolute/of/app/Model/OutroModelQualquer/Plugins/qualquer-outra-coisa.php';

That way, whenever you need to add functionality to Core and native framework functions, specific to business models, can do without appeals to gambiarras and destroying the application’s MVC concept.

The idea of a framework is to not need to mecher in your native routines even when you need to implement a very specific feature of a business model.

Roughly speaking, this logic with loading of Hooks/plugins, is an elegant gambiarra. What will make the process even more elegant is what OOP concept you will be able to implement under this scheme. At this point we will start talking about programming paradigms and very complex things. Try not to think about things that complicate. Keep the pattern KISS (Keep It Simple, Stupid).

*The above examples are purely didactic. Nomenclatures, filesystem structures, semantics, code pattern, method visibility, and logic themselves are merely illustrative.

Security

In this execution scheme of plugins/Hooks, one must be aware of security. It should be quite restrictive and organized about what a plugin can run.

So, never allow an ordinary (unauthorized) user to include a plugin, because it obviously opens gaps for malicious people or additional actions.

  • Very good suggestion, I will definitely test and try to apply it. Thank you!

1

I have a file attached to the application folders that is called appsconfig.php, in this file I added the following data:

'authentication' => [
   'controller' => 'Auth',
   'action' => 'logged',
   'redirect' => [
      'controller' => 'Login',
      'action' => 'index'
   ],
   'routes' => [ // Routes allowed
      'login' => ['index', 'authenticate']
   ]
]

There I put the controller and action responsible for verifying the authentication Auth::logged() that returns a boolean (this method is implemented by the application and not by the framework. I also placed the data for redirect redirect and routes allowed when not authenticated.

I created the method checkPermission in the Main Controller:

private function checkPermission() {

    if ($this->config->app->authentication) {

        $authentication = $this->config->app->authentication;

        $auth = "\\Controller\\{$authentication->controller}Controller";
        $check = $authentication->action;

        if (!($auth::$check())){
            if (!property_exists($authentication->routes, $this->request->controller)){
                $this->request->redirect(Route::href("{$authentication->redirect->controller}/{$authentication->redirect->action}"));
            }
            else if (
                property_exists($authentication->routes, $this->request->controller) && 
                !in_array($this->request->action, (array)$authentication->routes->{$this->request->controller})
            ){
                $this->request->redirect(Route::href("{$authentication->redirect->controller}/{$authentication->redirect->action}"));
            }
        }
    }

    return TRUE;
}

I know there’s a better way to do this, but for now you’re working it out. I will leave the question open, waiting for other possible solutions.

0

In this case there is a table in the database (Permissions) where the access permissions of the profiles are (Admin, Editor, Common), in this table is placed the addresses that the user can pass.. when the user is logged in the permissions are placed in $_SESSION and whenever accessing any address this routine defines whether the user has permission or not.

In the second part if there is no permission in Session it checks visitor permissions ($Routes = Routes::guest())

   if(isset($_SESSION['permissions']) && sizeof($_SESSION['permissions']) > 0){
        foreach ($_SESSION['permissions'] as $i => $value) {
            if(@preg_match($value['route'], $url) || $value['route'] == $url){
                return $value['allow'];
            }
        }
    }
    $routes = Routes::guest();
    foreach ($routes as $route => $array) {
        if(@preg_match($route, $url) || $route == $url){
            return $routes[$route];
        }
    }
    return false;

I just wanted to give you an idea of how it can be done, you need to make some adjustments and make as modular as possible, in this strategy I have three files working (index.php? url, Router::Mapping(), Permissions::check());

  • In case that code you posted is the Permissions::check()?

  • That’s right... in the case preg_match is used because the route can be described with regular expression as well, but $value['route'] == $url resolves to a route of type '/posts/add'

Browser other questions tagged

You are not signed in. Login or sign up in order to post.