User authentication is the basic functionality of the vast majority of web applications. This functionality is implemented using various programming languages and is supported by various repositories ("hardcode", files, databases, LDAP, ...).
In my previous publication, I expressed a bold misconception "In the meantime, the creation of the next web application often begins with the design of its own data structure for user authentication, " to which several references to some authentication implementations (mostly in PHP) were thrown to me. Under the cut - a comparison of the structures of User-models of these implementations.
Authentication is a functional familiar to every web developer. The simplest data structure for a User-model is something like this:
If the data is placed in a database, it is often supplemented by another (usually integer) attribute:
Well, let's see what the web developers offer various implementations of the basic functionality (I did not bring different structures to a single mind, but the essence is already clear). DISCLAIMER: I have not used these modules "in combat", my assumptions are based on the data structures under consideration - these are just my assumptions and nothing more. If the developers of the module in the field with the name email place the home address of the user, then my calculations will definitely mislead you.
The simplest scheme of the data reviewed:
CREATE TABLE user ( user_id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL UNIQUE, email VARCHAR(255) DEFAULT NULL UNIQUE, display_name VARCHAR(50) DEFAULT NULL, password VARCHAR(128) NOT NULL, state SMALLINT )
The minimum required set for the database (id, username, password), plus the identifiers "for people" (email, display_name), plus the user status code (active, inactive, ...). The uniqueness of the values by email suggests an idea of authentication by both username and email.
Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password', 60); $table->rememberToken(); $table->timestamps(); });
Also one of the most concise data schemes . The user is searched for "email", the minimum set of attributes of the User-model is supplemented by an attribute for the user name ("name" - display name). "rememberToken ()" most likely adds support for saving authentication for a specific browser ("Remember me" checkbox on the authentication form). "timestamps ()" presumably add dates for creating and modifying individual records (possibly deleting, but unlikely, because there is no state attribute - state, status, etc.)
FriendsOfSymfony / FOSUserBundle
<?xml version="1.0" encoding="UTF-8"?> <doctrine-mapping ...> <mapped-superclass name="FOS\UserBundle\Model\User"> <field name="username" column="username" type="string" length="180" /> <field name="usernameCanonical" column="username_canonical" type="string" length="180" unique="true" /> <field name="email" column="email" type="string" length="180" /> <field name="emailCanonical" column="email_canonical" type="string" length="180" unique="true" /> <field name="enabled" column="enabled" type="boolean" /> <field name="salt" column="salt" type="string" nullable="true" /> <field name="password" column="password" type="string" /> <field name="lastLogin" column="last_login" type="datetime" nullable="true" /> <field name="confirmationToken" column="confirmation_token" type="string" length="180" unique="true" nullable="true" /> <field name="passwordRequestedAt" column="password_requested_at" type="datetime" nullable="true" /> <field name="roles" column="roles" type="array" /> </mapped-superclass> </doctrine-mapping>
The data structure in FOSUserBundle additionally contains attributes that support resetting the user's password and saving the time of the last user authentication.
$this->createTable('{{%user}}', [ 'id' => $this->primaryKey(), 'username' => $this->string(25)->notNull(), 'email' => $this->string(255)->notNull(), 'password_hash' => $this->string(60)->notNull(), 'auth_key' => $this->string(32)->notNull(), 'confirmation_token' => $this->string(32)->null(), 'confirmation_sent_at' => $this->integer()->null(), 'confirmed_at' => $this->integer()->null(), 'unconfirmed_email' => $this->string(255)->null(), 'recovery_token' => $this->string(32)->null(), 'recovery_sent_at' => $this->integer()->null(), 'blocked_at' => $this->integer()->null(), 'registered_from' => $this->integer()->null(), 'logged_in_from' => $this->integer()->null(), 'logged_in_at' => $this->integer()->null(), 'created_at' => $this->integer()->notNull(), 'updated_at' => $this->integer()->notNull(), ], $this->tableOptions);
The most complex data structure examined. In addition to self-authentication ("auth_key" - remember-token?), There is confirmation of the email address, password recovery, session control ("logged_in_from" and "logged_in_at"), time for creating / changing user data.
The basic data model consists of two classes AbstractBaseUser and AbstractUser :
class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), blank=True, null=True) class AbstractUser(AbstractBaseUser, PermissionsMixin): ... username = models.CharField(_('username'), max_length=150, unique=True, ...) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=150, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField(_('staff status'), default=False, ...) is_active = models.BooleanField(_('active'), default=True, ...) date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
The minimum scheme is also sufficient, although it is spread over two classes. From the interesting - the attribute "is_staff", the user admission flag to the admin of the web application.
Very minimalistic data structure :
{ "name": "User", "properties": { "realm": { "type": "string" }, "username": { "type": "string" }, "password": { "type": "string", "required": true }, "email": { "type": "string", "required": true }, "emailVerified": "boolean", "verificationToken": "string" }, ... }
It supports verification of user emails and introduces an additional realm
attribute that allows users to be divided into "areas" (I think this is related to multitenant architecture, SaaS platforms ).
The data structure is also minimalistic:
private String password; private final String username; private final Set<GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled;
Expanded by a set of user rights and account status flags.
Ready data structures for user authentication exist both at the framework level / framework (Loopback, Django, Spring), and at the level of individual modules (ZF-Commons / ZfcUser, php-soft / laravel-users, FriendsOfSymfony / FOSUserBundle, dektrium / yii2-user) for the respective frameworks. There are no generalized data structures - each framework / module is repelled by "its own idea of the beautiful." Frameworks, as a rule, use structures with fewer attributes than modules, due to their greater versatility. But they initially provide for the possibility of expanding the basic structures in third-party modules that can implement alternative authentication schemes.
And finally, I would like to know how much I was mistaken about " designing my own data structures for user authentication. "
Source: https://habr.com/ru/post/320498/
All Articles