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 PrivateController
s 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]);
}
}
before_filter :authenticate_user, only: [:new, :create, :edit, :update, :destroy]
which would require authentication for those methods in the controller.UseCase
/Action
). See how "middlewares" in popular frameworks are implemented or read aboutChain of responsibility pattern
(if you like to keep frameworks on distance).