Authentication and level based authorization for PHP.
Install using composer
composer require jasny\auth
Jasny\Auth is an abstract class. You need to extend it and implement the abstract methods fetchUserById and
fetchUserByUsername.
You also need to specify how the current user is persisted across requests. If you want to use normal PHP sessions, you
can simply use the Auth\Sessions trait.
class Auth extends Jasny\Auth
{
use Auth\Sessions;
/**
* Fetch a user by ID
*
* @param int $id
* @return User
*/
public static function fetchUserById($id)
{
// Database action that fetches a User object
}
/**
* Fetch a user by username
*
* @param string $username
* @return User
*/
public static function fetchUserByUsername($username)
{
// Database action that fetches a User object
}
}The fetch methods need to return a object that implements the Jasny\Auth\User interface.
class User implements Jasny\Auth\User
{
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $username;
/**
* Hashed password
* @var string
*/
public $password;
/**
* @var boolean
*/
public $active;
/**
* Get the user ID
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Get the usermame
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Get the hashed password
*
* @return string
*/
public function getHashedPassword()
{
return $this->password;
}
/**
* Event called on login.
*
* @return boolean false cancels the login
*/
public function onLogin()
{
if (!$this->active) {
return false;
}
// You might want to log the login
}
/**
* Event called on logout.
*/
public function onLogout()
{
// You might want to log the logout
}
}By default the Auth class only does authentication. Authorization can be added by implementing the Authz interface.
Two traits are predefined to do Authorization: Authz\ByLevel and Authz\ByGroup.
The Authz\ByLevel traits implements authorization based on access levels. Each user get permissions for it's level and
all levels below.
class Auth extends Jasny\Auth implements Jasny\Authz
{
use Jasny\Authz\ByLevel;
protected function getAccessLevels()
{
return [
1 => 'user',
10 => 'moderator',
20 => 'admin',
50 => 'superadmin'
];
}
}If you get the levels from a database, make sure to save them in a property for performance.
class Auth extends Jasny\Auth implements Jasny\Authz
{
use Jasny\Authz\ByGroup;
protected $levels;
protected function getAccessLevels()
{
if (!isset($this->levels)) {
$this->levels = [];
$result = $this->db->query("SELECT name, level FROM access_levels");
while (($row = $result->fetchAssoc())) {
$this->levels[$row['name']] = (int)$row['level'];
}
}
return $this->levels;
}
}For authorization the user object also needs to implement Jasny\Authz\User, adding the getRole() method. This method
must return the access level of the user, either as string or as integer.
/**
* Get the access level of the user
*
* @return int
*/
public function getRole()
{
return $this->access_level;
}The Auth\ByGroup traits implements authorization using access groups. An access group may supersede other groups.
You must implement the getGroupStructure() method which should return an array. The keys are the names of the
groups. The value should be an array with groups the group supersedes.
class Auth extends Jasny\Auth implements Jasny\Authz
{
use Jasny\Authz\ByGroup;
protected function getGroupStructure()
{
return [
'users' => [],
'managers' => [],
'employees' => ['user'],
'developers' => ['employees'],
'paralegals' => ['employees'],
'lawyers' => ['paralegals'],
'lead-developers' => ['developers', 'managers'],
'firm-partners' => ['lawyers', 'managers']
];
}
}If you get the structure from a database, make sure to save them in a property for performance.
class Auth extends Jasny\Auth implements Jasny\Authz
{
use Jasny\Authz\ByGroup;
protected $groups;
protected function getGroupStructure()
{
if (!isset($this->groups)) {
$this->groups = [];
$result = $this->db->query("SELECT ...");
while (($row = $result->fetchAssoc())) {
$this->groups[$row['group']] = explode(';', $row['supersedes']);
}
}
return $this->groups;
}
}For authorization the user object also needs to implement Jasny\Authz\User, adding the getRole() method. This method
must return the role of the user or array of roles.
/**
* Get the access groups of the user
*
* @return string[]
*/
public function getRoles()
{
return $this->roles;
}By using the Auth\Confirmation trait, you can generate and verify confirmation tokens. This is useful to require a
use to confirm signup by e-mail or for a password reset functionality.
You need to add a getConfirmationSecret() that returns a string that is unique and only known to your application.
Make sure the confirmation secret is suffiently long, like 20 random characters. For added security, it's better to
configure it through an environment variable rather than putting it in your code.
class Auth extends Jasny\Auth
{
use Jasny\Auth\Confirmation;
public function getConfirmationSecret()
{
return getenv('AUTH_CONFIRMATION_SECRET');
}
}The confirmation token exists of the user id and a checksum, which is obfuscated using hashids.
A casual user will be unable to get the userid from the hash, but hashids is not a true encryption algorithm and with enough tokens a hacker might be able to determine the salt and extract the user id and checksum from tokens. Note that knowing the salt doesn't mean you know the configured secret.
The checksum is the first 16 bytes of the sha256 hash of user id + secret. For better security you might add want to use more than 12 characters. This does result in a larger string for the token.
class Auth extends Jasny\Auth
{
...
protected function getConfirmationChecksum($id, $len = 32)
{
return parent::getConfirmationChecksum($id, $len);
}
...
}Verify username and password
boolean verify(User $user, string $password)
Login with username and password
User|null login(string $username, string $password);
Set user without verification
User|null setUser(User $user)
If $user->onLogin() returns false, the user isn't set and the function returns null.
Logout
logout()
Get current user
User user()
You can apply access control manually using the is() method. Alteratively, if you're using a PSR-7 compatible router
with middleware support (like Jasny Router]).
$auth = new Auth(); // Implements the Jasny\Authz interface
$roure->add($auth->asMiddleware(function(ServerRequest $request) {
$route = $request->getAttribute('route');
return isset($route->auth) ? $route->auth : null;
}));Check if a user has a specific role or superseding role
boolean is(string $role)
if (!$auth->is('admin')) {
http_response_code(403);
echo "You're not allowed to see this page";
exit();
}Get a verification token. Use it in an url and set that url in an e-mail to the user.
// Create a new $user
$auth = new Auth();
$confirmationToken = $auth->getConfirmationToken($user, 'signup');
$host = $_SERVER['HTTP_HOST'];
$url = "http://$host/confirm.php?token=$confirmationToken";
mail(
$user->getEmail(),
"Welcome to our site",
"Please confirm your account by visiting $url"
);Use the confirmation token to fetch and verify the user
// --- confirm.php
$auth = new Auth();
$user = $auth->fetchUserForConfirmation($_GET['token'], 'signup');
if (!$user) {
http_response_code(400);
echo "The token is not valid";
exit();
}
// Process the confirmation
// ...Get a verification token. Use it in an url and set that url in an e-mail to the user.
Setting the 3th argument to true will use the hashed password of the user in the checksum. This means that the token
will stop working once the password is changed.
// Fetch $user by e-mail
$auth = new MyAuth();
$confirmationToken = $auth->getConfirmationToken($user, 'reset-password', true);
$host = $_SERVER['HTTP_HOST'];
$url = "http://$host/reset.php?token=$confirmationToken";
mail(
$user->getEmail(),
"Welcome to our site",
"Please confirm your account by visiting $url"
);Use the confirmation token to fetch and verify resetting the password
$auth = new MyAuth();
$user = $auth->fetchUserForConfirmation($_GET['token'], 'reset-password', true);
if (!$user) {
http_response_code(400);
echo "The token is not valid";
exit();
}
// Show form to set a password
// ...