Auth - authentication and authorization
The authentication and authorization system in Jet has unified abstract interfaces for performing basic operations for user login, authentication, and authorization control for any operations.
What does this mean in practice? In almost every application we need some users to be able to log in, we also need to check if they are logged in, if their account is valid and if so, if they are allowed to do exactly what they are going to do. This is actually always the same, it's always the same authentication and authorization operations. What differs is the low-level implementation itself. For example, where are user accounts stored and how? Is it in the database? What is the structure of the table? Or is user account information not stored in the application database at all and access is controlled by the corporate network? What about logging in using third-party services? And of course, the spectrum of information we record about users and so on can be fundamentally different.
What the user entity looks like and where and how it is stored and how its login is verified is incredibly variable and diverse. And at the same time, it must be possible to implement anything. But the rest of the application doesn't really care if the user is in the database or somewhere else. No, the rest of the application just needs to know: the user is / is not logged in, their account is / is not valid, and they may / may not perform this operation. So, no matter what is complicated in the background, there is actually a simple interface on the surface and again a facade and a container holding the controller, which will take care of everything. From the point of view of using applications, it is therefore a very simple system that is concise and clear. But which has the task of covering anything and does not impose any restrictions.
Let's first talk about what we will encounter in Jet Auth:
- Controller
The controller controls the authentication and authorization logic.
Thus, it controls the logic of login, logout, verification of user validity and verification of whether the user has the necessary authorization (see below).
The controller represents the interface Jet\Auth_Controller_Interface .
- User
A user is an entity that must have a few basic general properties and methods (for example, every user has a username) and which, of course, can be expanded arbitrarily in the application. The entity takes care of the actual loading and storage of information about the user, no matter where the data is stored in any form - this is already an internal matter of the entity.
Represented by Jet\Auth_User_Interface .
- Role
Permissions (to operations) in Jet are handled by the role system. There can be any number of roles and each user can be in any number of roles. (authorization then escalates - adds up)
Naturally, there must be a role entity that has defined basic methods, but which can be expanded arbitrarily in the application.
Represented by Jet\Auth_Role_Interface . - Authorization
An authorization is a sub-entity of a role and represents a certain authorization (to some operation). Of course, a role can have any number of permissions.
Represented by Jet\Auth_Role_Privilege_Interface .
- The main facade
It is through this class and its static methods that the application works with the authentication and authorization system.
Represented by the Jet\Auth class
Initialization
Since the Auth system is only an empty container, it is necessary to "fill" it first, i.e. to say which controller should be used to control the logic. Controller implementation is purely an application matter. So in your application, you must have a class (in practice, rather classes) that implements the Jet\Auth_Controller_Interface interface.
You won't find any prepackaged controller in Jet (in the library), but sample controllers are part of the sample application. These are the classes:
- JetApplication\Auth_Controller_Admin
- JetApplication\Auth_RESTClient_User
- JetApplication\Auth_Controller_Web
If you are already familiar with Jet MVC , then you will surely already know that there are different controllers for different bases . So logging into the administration is completely different from logging into the REST API client. And then you can probably guess that it will be some kind of base initializer , and that's exactly what it is - we'll get to that right away.
No Auth controller is initialized at the beginning of the application run. No, Jet Auth must be initialized only when you know what the situation is and what kind of controller you will need. If using Jet MVC , the initialization is done in the base initializer. So for example like this:
namespace JetApplication;
use Jet\MVC_Router;
use Jet\Auth;
// ... ... ... ...
class Application_Admin
{
// ... ... ... ...
public static function init( MVC_Router $router ): void
{
// ... ... ... ...
Auth::setController( new Auth_Controller_Admin() );
// ... ... ... ...
}
// ... ... ... ...
}
If you're not going to make a Jet MVC based application, then it's up to you where you do the initialization. But it is always necessary to do something like this:
Auth::setController( new MyMegaCoolAuthController() );
Use
As already mentioned, the authentication and authorization system is used through the Jet\Auth facade. Let's show it practically now.
Is anyone logged in?
If you need to verify that a user is logged in and their user account is valid (for example, they haven't been blocked), then just do this:
if( Auth::checkCurrentUser() ) {
//It's OK
}
Who is logged in?
It is often necessary to find out who is logged in and find out some information about them:
if( ( $user = Auth::getCurrentUser() ) ) {
echo 'Current user: '.$user->getUsername();
}
Login
You have a username and password available, you want to verify it and if the data is correct, then log the user in:
if( Auth::login( $username, $password ) ) {
echo 'Logged in ...';
}
Log out
Based on some action (Jet doesn't decide what - that's a matter of your application) the user needs to be logged out:
Auth::logout();
Login service
The user is not logged in and you want the controller to take care of the login. In practice, this means displaying the login form instead of the content. How and what the controller does is purely a matter for the controller.
if( Auth::checkCurrentUser() ) {
Auth::handleLogin();
}
Permission check - can the user do "this and that"?
We have already said that authorization control is solved in the form of roles and their rights. We will return to this later, but for now let's show how to verify whether the user has the necessary right. This is already very simple.
You have defined permissions for viewing documents in the application, and you want to know whether the user has permission to see a specific document:
if( Auth::getCurrentUserHasPrivilege( 'view-document', $document_id ) ) {
//It's OK
}
This was a completely arbitrary and self-defined permission check. However, Jet has two basic permissions built in:
Authorization to visit the site :
$page = MVC::getPage('some-page-id');
if( Auth::checkPageAccess( $page ) ) {
//It's OK
}
Authorization to the ACL action of the application module :
if( Auth::checkModuleActionAccess('Some.Module', 'module-action') ) {
//It's OK
}
Note: Of course, the authorization check must first verify whether the user is logged in and his account is active.
User
The user entity must meet at least what the interface Jet\Auth_User_Interface declares . Importantly, only the functional minimum is defined by the interface. Of course, your user implementation can have any other properties. Whether it's an address, job title, contacts - just about anything. That is entirely up to you. For a closer look at what Jet Auth requires, I recommend getting to know the relevant interface .
Roles and permissions
The same applies to roles as to users. So the role must meet at least what is required by the relevant interface Jet\Auth_Role_Interface . If you add, for example, additional internal notes, icons, or anything else you need to the role, then it's perfectly fine and it should be that way.
However, in the context of roles, it is good to explain how the authorization system works .
Permissions feature
As already said, the so-called privileges that are represented by the Jet\Auth_Role_Privilege_Interface interface fall under the role. Authorization is something you can and should define yourself, according to the needs of your application. Even though Jet has two already built-in permissions for visiting a page and performing an application module action - the other permissions are entirely up to you. The permission is identified by a text string.
Let's show it directly with an example. Let's say that you are creating a company intranet and you need to manage who can edit which internal company articles. So you tell yourself to create the 'edit-interanet-article' permission. So we have authorization, the existence of which entitles us to something - the execution of some operation in general. However, with an intranet, it can be assumed that not everyone will be able to edit everything, i.e. that there will be a limited range of articles that a given role can edit. The fact that the 'edit-interanet-article' permission exists is not enough in itself. This permission will refer to something. Therefore, the authorization consists of two parts. The first part is the permission name, which we have already defined in our example ('edit-interanet-article'). The second part of the authorization is the value . And it is the value of the authorization that makes it concrete. One permission has one name, but can have any number of values. The value is actually an array of allowed values .
So the role has the permission 'edit-interanet-article' and this permission has X values. In our example, these values will be the IDs of the articles that are on the intranet.
And let's move theory closer to practice. Of course, there can be any number of roles in the system. Each role is again identified by a text string (for clarity and usability). Our hypothetical internet has, in addition to a number of other roles, the roles 'editor-HR' and 'editor-accountant'. And both of these roles can have 'edit-interanet-article' permission. As well as this privilege, it may (or may not) have any other roles. The authorization is therefore not strictly bound to the role, but on the contrary is shared between the roles.
However, the difference is that the 'editor-HR' role has different permission values than the 'editor-accountant' role. Thus, the 'editor-HR' role, using the 'edit-interanet-article' permission, allows you to edit articles 1, 8 and 9, while the 'editor-accountant' role allows you to edit articles 2, 3 and 11, whose IDs are the values of the given permission. So both roles allow you to edit articles, but each one is completely different (or maybe even common and identical - of course that's also possible). You have everything solved in the administration of your application and it can be clicked. How specifically it is up to you - Jet does not dictate this, it only provides a sample and inspiration in sample applications.
Now let's go back to users from roles and permissions. Each user can be in any number of roles. Thus, the hypothetical user MarieM is in the role of editor-HR, the user JanH is in the role of editor-accountant. This means that MarieM can edit articles 1,8 with 9 and user JanH can edit articles 2,3 and 11. But then there can be a user in the system, whom we call VelkySef, for example. And this user has more roles. In addition to other roles, he has the role of 'editor-HR' and 'editor-accountant'. This means he can edit articles 1,2,3,8,9 and 11. The authorization is therefore gradually added up and increased.
Classes and modules in the sample application
The sample application with which Jet is distributed includes several sample implementations of the necessary classes and even administration modules. These classes and modules are not intended to be definitive. It is only an example and perhaps even a basis, which you can of course use as needed.
Controllers
- JetApplication\Auth_Controller_Admin
Controller intended for controlling access to the administration. All functions are implemented in full. - JetApplication\Auth_Controller_Web
Controller for managing access to "passworded" parts of the website. It deliberately does not allow to verify module actions (the checkModuleActionAccess method always returns false). It is a demonstration that the implementation of the controller should fully reflect its purpose. - JetApplication\Auth_Controller_REST
REST API server specific controller. Again, it has several specifics. It does not use a session at all for user login, but always authenticates the user based on HTTP authentication. So it uses a completely different procedure than the previous two controllers.
Users
- JetApplication\Auth_Administrator_User
The class represents the administration user. Users are conventionally stored in a database, so this is a DataModel . - JetApplication\Auth_Visitor_User
A class representing a website visitor who is registered and can access non-public parts/sections. Conventional database storage again. - JetApplication\Auth_RESTClient_User
This class represents the REST API server client user account. Here, too, it is a matter of conventional storage in the database.
Roles and permissions
- JetApplication\Auth_Administrator_Role
JetApplication\Auth_Administrator_Role_Privilege
Classes represent the role and rights of the administration user. Even the roles are conventionally stored in the database and are therefore a DataModel . - JetApplication\Auth_Visitor_Role
JetApplication\Auth_Visitor_Role_Privilege
Classes representing the role and rights of a website visitor who is registered and can access non-public parts / sections. Conventional database storage as well. - JetApplication\Auth_RESTClient_Role
JetApplication\Auth_RESTClient_Role_Privilege
These classes represent the roles and rights of the REST API server client user account. This is also conventional database storage.
User connection to roles
Of course, it is necessary to determine which user is in which role. The following classes are used for this. All of them are normal database tables and everything again uses the DataModel . Neither class has anything specific, so just an enumeration:
- JetApplication\Auth_Administrator_User_Roles
- JetApplication\Auth_Visitor_User_Roles
- JetApplication\Auth_RESTClient_User_Roles
Modules - login
The controller must be able to handle the state when the user is not logged in and prompt the user to log in. For example, display the login page / form. But that's not all. If the user is blocked, it is again necessary to display the relevant information to the user in a friendly form. What if the user's password has expired? Or how about doing two-factor login? How about a forgotten password solution?
In short, there are many situations that can arise. And it is up to the controller to solve this. However, precisely because of the very extensive logic, the controller classes themselves do not solve all this in the example application, but on the contrary pass the detected situation to the application modules to be solved. So, simply put, the controllers find out what is happening and what needs to be done, and then let the application module do it in MVC mode. How exactly? Take a look at the handleLogin() methods of one of the controllers (classes JetApplication\Auth_Controller_Admin or JetApplication\Auth_Controller_Web).
And what sample application modules handle login and a variety of other related operations in the sample application? They are:
- Login.Admin
- Login.Web
Modules - role and user management
In practice, of course, it is necessary to manage users and their roles (add, cancel, set...). You can also find a sample solution for such things in the sample application. Thus, in administration you will find application modules for managing all three types of users and roles that are part of the sample application. These modules are:
- ManageAccess.Administrators.Roles
- ManageAccess.Administrators.Users
- ManageAccess.Visitors.Roles
- ManageAccess.Visitors.Users
- ManageAccess.RESTClients.Roles
- ManageAccess.RESTClients.Users
You can not only try all modules and test everything on them, but you can use them as a basis for your solution.