logo

Tutorials: Authenticating and Authorizing

Setting Authentication and Authorization

Framework supports form based and OAuth2 based (eg: Facebook) authentication, using databases or XML to store users and authorize access to resources. To make any of above possible, you need to open bootstrap index.php file and add this event listener:

$object->addEventListener(Lucinda\STDOUT\EventType::REQUEST, Lucinda\Project\EventListeners\Security::class);

then create a tag in XML root where authentication, authorization and state persistence are configured. To learn more how to configure this tag, check official documentation!

Configuring Form-based Authentication

Below example sets up a csrf token protected form based authentication where credentials are checked in database (by Users class in src/DAO folder) and access rights in XML while logged in state persists in session as uid param:

<security> <csrf secret="SECRET_KEY"/> <authentication> <form dao="Lucinda\Project\DAO\UserAuthentication" throttler="Lucinda\Project\DAO\NoSqlLoginThrottler"/> </authentication> <persistence> <session/> </persistence> <authorization> <by_route/> </authorization> </security>

If you desire to use XML-based authentication instead, simply remove dao attribute above and make sure tag is properly set in stdout.xml!

Generating Secret

To prevent cross site scripting, make sure value of secret attribute is unique for your site! You can use to generate it:

$saltGenerator = new \Lucinda\WebSecurity\Token\SaltGenerator(128); $secret = $saltGenerator->getSalt();

Configuring Login Throttler

To prevent password-guessing through brute-force attacks, it is required to provide a throttler attribute pointing to a class in src/DAO folder that ultimately extends .

If you're using a implementation provided by framework, it will ban wrongdoer from attempting to log in for 2ATTEMPTS_NUMBER-1 seconds at every consecutive failure. Framework comes already with following implementations:

Configuring Routes

Form based authentication requires at least following routes being set:

Example setting:

<routes roles="GUEST"> <route id="index" controller="Lucinda\Project\Controllers\Index" view="index" roles="GUEST,MEMBER"> <route id="login" controller="Lucinda\Project\Controllers\Login" view="login" roles="GUEST"> <route id="logout" roles="MEMBER"> ... </routes>

Configuring OAuth2-based Authentication

Below example sets up a oauth2 based authentication, using database to store access tokens and check access rights (by Users class in src/DAO folder) then persisting logged in state in session as uid:

<security> <authentication> <oauth2 dao="Lucinda\Project\DAO\UserAuthentication"/> </authentication> <persistence> <session/> </persistence> <authorization> <by_route/> </authorization> </security>

OAuth2 based authentication requires tag already filled (see tutorial for more info)!

Configuring Routes

OAuth2 based authentication also requires at least following routes being set

Example setting:

<routes roles="GUEST,MEMBER"> <route id="index" controller="Lucinda\Project\Controllers\Index" view="index" roles="GUEST,MEMBER"> <route id="login/facebook" roles="GUEST"> <route id="login/google" roles="GUEST"> <route id="logout" roles="MEMBER"> ... </routes>

Configuring Authorization

Above examples use XML-based authorization via <by_route> tag, which fits the needs of pretty much all normal sites. If you are developing a CMS and desire to use database-based authorization instead, use <by_dao> instead:

<authorization> <by_dao page_dao="Lucinda\Project\DAO\PagesAuthorization" user_dao="Lucinda\Project\DAO\UsersAuthorization"/> </authorization>

If you are using this authorization method, roles attribute @ is not needed!

Persisting Logged-in State

In order to preserve logged in state across requests, you must work on tag. Example:

<persistence> <session/> <remember_me secret="P*tD:MKpTeBU?K]"/> </persistence>

This persists logged in state in both session and remember me cookie, protected against cross site request forgery through an expiring secret token bound to ip. To prevent cross site scripting, make sure value of secret is unique for your site!

Authenticating Users

After you have completed Setting Authentication and Authorization section described above, you have a tag that sets up authentication, pointing to classes developers must implement in order for framework to handle authentication.

Form Authentication Using Database

Assuming XML is same as in Setting Authentication and Authorization:

<security> ... <authentication> <form dao="Lucinda\Project\DAO\UsersFormAuthentication" throttler="Lucinda\Project\DAO\NoSqlLoginThrottler"/> </authentication> ... </security>

Above sets a form authentication, where credentials are checked in database by class referenced by dao attribute in src/DAO folder implementing .

DEMO

Demo implementation example @ Framework Configurer API:

Above example assumes following recommended minimal MySQL table structure:

CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, PRIMARY KEY(id), UNIQUE(username) ) Engine=INNODB;

CSRF Token

In order to prevent cross site request forgery, login forms must also include a CSRF token:

<form action="" method="POST"> Username: <input type="text" name="username" required/> Password: <input type="password" name="password" required/> <input type="checkbox" name="remember_me" value="1" checked/> Remember me <input type="hidden" name="csrf" value="${data.csrf}"/> <input type="submit" value="LOGIN"/> <a href="/">HOMEPAGE</a> </form>

Where value of csrf must sent to view by controller as below:

class LoginController extends Lucinda\STDOUT\Controller { public function run(): void { $this->response->view()["csrf"] = $this->attributes->getCsrfToken(); } }

Form Authentication Using XML

Assuming XML is same as in Setting Authentication and Authorization:

<security> ... <authentication> <form throttler="Lucinda\Project\DAO\NoSqlLoginThrottler"/> </authentication> ... </security>

You need to set existing users in tag @ stdout.xml. Example:

<users> <user id="1" username="john" password="PASSWORD" roles="MEMBER"/> <user id="2" username="mark" password="PASSWORD" roles="MEMBER,ADMINISTRATOR"/> </users>

Where each user PASSWORD in XML must be a password_hash result:

$passwordToSave = password_hash($originalPassword, PASSWORD_DEFAULT);

OAuth2 Authentication Using Database

Assuming XML is same as in Setting Authentication and Authorization:

<security> ... <authentication> <oauth2 dao="Lucinda\Project\DAO\UsersOAuth2Authentication"/> </authentication> ... </security>

Above sets a OAuth2 authentication where access token is saved in database by class referenced by dao attribute in src/DAO folder implementing .

DEMO

Demo implementation example @ Framework Configurer API:

Above example assumes following recommended minimal MySQL table structure:

CREATE TABLE oauth2_providers ( id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, PRIMARY KEY(id), UNIQUE(name) ) Engine=INNODB; INSERT INTO oauth2_providers (name) VALUES ('Facebook'), ('Google'), ('GitHub'), ('Instagram'), ('LinkedIn'), ('VK'), ('Yahoo'), ('Yandex'); CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, driver_id TINYINT UNSIGNED NOT NULL, remote_user_id VARCHAR(32) NOT NULL, access_token TEXT NOT NULL, PRIMARY KEY(id), FOREIGN KEY(driver_id) REFERENCES oauth2_providers(id), UNIQUE(email), UNIQUE(driver_id, remote_user_id) ) Engine=INNODB CHARACTER SET utf8 COLLATE utf8_bin;

Both Form & OAuth2 Authentication Using Database

Some sites require you to support both form and oauth2 methods of authentication. In that case XML should be like:

<security> ... <authentication> <form dao="Lucinda\Project\DAO\UsersFormAuthentication" throttler="Lucinda\Project\DAO\NoSqlLoginThrottler"/> <oauth2 dao="Lucinda\Project\DAO\UsersOAuth2Authentication"/> </authentication> ... </security>

where DAO classes, located in folder src/DAO.

DEMO

Demo implementation examples @ Framework Configurer API:
+

assuming following MySQL table structure:

CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, PRIMARY KEY(id), UNIQUE(email) ) Engine=INNODB CHARACTER SET utf8 COLLATE utf8_bin; CREATE TABLE users__form ( user_id INT UNSIGNED NOT NULL, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(user_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(username) ) Engine=INNODB CHARACTER SET utf8 COLLATE utf8_bin; CREATE TABLE users__oauth2 ( user_id INT UNSIGNED NOT NULL, driver_id TINYINT UNSIGNED NOT NULL, remote_user_id VARCHAR(32) NOT NULL, access_token TEXT NOT NULL, PRIMARY KEY(user_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(driver_id) REFERENCES oauth2_providers(id) ON DELETE CASCADE, UNIQUE(driver_id, remote_user_id) ) Engine=INNODB;

Getting Logged In User ID

After authentication is performed, a is thrown for STDERR MVC API to handle via which will issue redirections on success/failure. If successful, you can from now on get id of logged in user in controller or subsequent event listener using:

$userID = $this->attributes->getUserId();

Once user logs in, its id is saved into persistence drivers (eg: session, cookie) defined in tag.

Authorizing User Access to Site Resources

After you have completed Setting Authentication and Authorization section described above, you already have a tag that sets up authorization depending on solution chosen. Once a request is received, if user is authorized access to requested site resource, execution continues. Otherwise, a is thrown for STDERR MVC API to handle results (eg: issue redirections).

Authorization Using XML

Assuming XML is same as in Setting Authentication and Authorization:

<security> ... <authorization> <by_route/> </authentication> ... </security>

Above sets an authorization where access rights are checked in XML based on contents of tag @ stdout.xml. This makes homepage accessible by all users, login only for unauthenticated guests, logout/members only for authenticated users.

Matching route roles to roles held by user depends on authentication solution chosen:

Authorization Using Database

Assuming XML is same as in Setting Authentication and Authorization:

<security> ... <authorization> <by_dao page_dao="Lucinda\Project\DAO\PagesAuthorization" user_dao="Lucinda\Project\DAO\UsersAuthorization"> </authentication> ... </security>

Above sets a DAO authorization, where access rights are matched in database by classes referenced by page_dao and user_dao attributes found in src/DAO folder and extending and respectively.

DEMO

Demo implementation examples @ Framework Configurer API:
+

assuming following MySQL table structure:

CREATE TABLE users_resources ( id int unsigned not null auto_increment, user_id int unsigned not null, resource_id smallint unsigned not null, PRIMARY KEY(id), foreign key(user_id) references users(id) on delete cascade, foreign key(resource_id) references resources(id) on delete cascade ) Engine=INNODB
×