mmdm/sim-auth

A simple yet nice authentication library

v0.2.17 2022-09-30 00:06 UTC

This package is auto-updated.

Last update: 2024-03-29 04:06:06 UTC


README

A library for authentication.

Install

composer

composer require mmdm/sim-auth

Or you can simply download zip file from github and extract it, then put file to your project library and use it like other libraries.

Just add line below to autoload files:

require_once 'path_to_library/autoloader.php';

and you are good to go.

But Wait

Why rough it when composer is here to help us. Please use composer to enjoy this library ☻

Architecture

This library is mostly for role-based and resource-based architecture together. Look at below information:

Collation:

It should be utf8mb4_unicode_ci because it is a very nice collation. For more information about differences between utf8 and utf8mb4 in general and unicode please see this link from stackoverflow

Note: Please use utf8mb4 collations.

Table:

  • users

    This table contains all users.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • username (VARCHAR(20) NOT NULL)

    • password (VARCHAR(255) NOT NULL)

  • api_keys

    This table is to store all api users and the keys.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • username (VARCHAR(20) NOT NULL)

    • api_key (VARCHAR(255) NOT NULL)

  • roles

    This table contains all roles.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • name (VARCHAR(20))

    • description (VARCHAR(100))

    • is_admin (TINYINT(1) UNSIGNED NOT NULL DEFAULT 0)

  • resources

    This table contains all resources.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • name (VARCHAR(20))

    • description (VARCHAR(100))

  • sessions

    This table contains all UUIDs that generate for a user.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • uuid (VARCHAR(36))

    • user_id (INT(11) UNSIGNED NOT NULL)

    • ip_address (VARCHAR(16))

    • device (TEXT)

    • browser (TEXT)

    • platform (TEXT)

    • expire_at (INT(11) UNSIGNED)

    • created_at (INT(11) UNSIGNED)

Table Relations:

  • user_role

    This table contains all users and their roles.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • user_id (INT(11) UNSIGNED NOT NULL)

    • role_id (INT(11) UNSIGNED NOT NULL)

    Constraints:

    • ADD CONSTRAINT fk_urp_u FOREIGN KEY(user_id) REFERENCES users(id)

    • ADD CONSTRAINT fk_urp_r FOREIGN KEY(role_id) REFERENCES roles(id)

  • user_res_perm

    This table contains all users, resources and their permission.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • user_id (INT(11) UNSIGNED NOT NULL)

    • resource_id (INT(11) UNSIGNED NOT NULL)

    • perm_id (INT(11) UNSIGNED NOT NULL)

    • is_allow (TINYINT(1) UNSIGNED NOT NULL DEFAULT 1)

    Constraints:

    • ADD CONSTRAINT fk_upp_u FOREIGN KEY(user_id) REFERENCES users(id)

    • ADD CONSTRAINT fk_upp_pa FOREIGN KEY(resource_id) REFERENCES resources(id)

  • role_res_perm

    This table contains all roles, resources and their permission.

    Least columns of this table should be:

    • id (INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT)

    • role_id (INT(11) UNSIGNED NOT NULL)

    • resource_id (INT(11) UNSIGNED NOT NULL)

    • perm_id (INT(11) UNSIGNED NOT NULL)

    Constraints:

    • ADD CONSTRAINT fk_rpp_r FOREIGN KEY(role_id) REFERENCES roles(id)

    • ADD CONSTRAINT fk_rpp_pa FOREIGN KEY(resource_id) REFERENCES resources(id)

How to use

First of all you need a PDO connection like below:

$host = '127.0.0.1';
$db = 'database name';
$user = 'username';
$pass = 'password';
// this is very nice collation to use
$charset = 'utf8mb4';

$dsn = "mysql:host={$host};dbname={$db};charset={$charset}";
$options = [
    // add this option to show exception on any bad condition
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
];
try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    throw new \PDOException($e->getMessage(), (int)$e->getCode());
}

[Optionally] Second you need two keys to protect stored credentials. These two keys should be two base64 coded strings. Just generate two passwords and encode them to base64 strings. For more info about these two keys see this link

Available Connections

  • DBAuth

  • APIAuth

Shared Methods

AbstractBaseAuth

All connections have below methods:

__construct(PDO $pdo_instance, ?array $config = null);

$pdo_instance: Argument is PDO connection that explained above.

$config: To change the Architecture mentioned above, you can pass and array to change the config or null for default behavior.

setConfig(array $config, bool $merge_config = false)

With this method you can change Architecture signatures. If you need to merge default configuration and passed configuration, you can pass true as seconds parameter.

runConfig()

The configuration can build internally by library. Thi function parse the configuration of library.

Note: If you build tables from configuration by yourself, there is no need to call this method, but please before any table creation, call this method to make your tables.

addResources(array $resources)

Add some resources to database.

$resources array has following structure:

// an array of arrays 
[
  // this array is columns of resource table and their values
  [
    column1 => value1,
    column2 => value2,
  ],
  [
    column1 => value3,
    column2 => value4,
  ],
  ...
]

removeResources(array $resources)

Remove some resources from database.

Note: $resources should be array of resources' name or resources' id.

hasResource($resource): bool

Check if a resource is exists or not.

Note: $resources should be array of resources' name or resources' id.

getResources(): array

Get all resources.

Note: It returns all columns of resources table.

getResourcesNames(): array

Get all resources name column.

addRoles(array $roles)

Add some roles to database.

$roles array has following structure:

[
  // this array is columns of role table and their values
  [
    column1 => value1,
    column2 => value2,
  ],
  [
    column1 => value3,
    column2 => value4,
  ],
  ...
]

removeRoles(array $roles)

Remove some roles from database.

Note: $roles should be array of roles' name or roles' id.

hasRole(string $role): bool

Check if a role is exists or not. You must pass the role name as parameter. It is because of convenient to use constants as roles' names.

getRoles(): array

Get all roles.

Note: It returns all columns of roles table.

getAdminRoles(): array

Get all admin roles.

Note: It returns all columns of roles table.

getRolesName(): array

Get all roles name column.

getAdminRolesName(): array

Get all admin's roles name column.

allowRole($resource, array $permission, $role)

Make a role to allow a resource with a specific permission.

Note: You can pass resource's name or id.

Note: You can pass role's name or id.

disallowRole($resource, array $permission, $role)

Make a role to disallow a resource with a specific permission.

Note: You can pass resource's name or id.

Note: You can pass role's name or id.

AbstractAuth

Some connections (that will say in each connection) have below methods:

__construct(
    PDO $pdo_instance,
    string $namespace = 'default',
    array $crypt_keys = [],
    int $storage_type = IAuth::STORAGE_DB,
    ?array $config = null
);

$pdo_instance: See constructor of AbstractBaseAuth.

$namespace: You can specify a namespace to separate each logic of your application. For example a home namespace for users login and an admin namespace for administrators logic.

$crypt_keys: To protect your cookies and sessions information, you can specify two keys on below structure:

[
    'main' => main key of encryption,
    'assured' => assured key of encryption
]

If you don't need any encryption (that is not suggested), you can pass an empty array as value of the parameter.

$storage_type: The storage type tell authentication library to store needed information in where. There are three types of storage:

  • IAuth::STORAGE_SESSION

    This storage type, store data in the php session

  • IAuth::STORAGE_COOKIE

    This storage type, store data in the cookie

  • IAuth::STORAGE_DB (Suggested)

    This storage type, store data in the database with a uuid

(They are available under Sim\Auth\Interfaces namespace)

$config: See constructor of AbstractBaseAuth.

getStatus(): int

There are four types of status for better management of authentication:

  • IAuth::STATUS_NONE

    When there is no session/cookie or anything, the status is none

  • IAuth::STATUS_ACTIVE

    When a user logged in to his/her account, the status will be active

  • IAuth::STATUS_EXPIRE

    After a user login it'll set a session/cookie for expiration time of login. If it expires, it'll change status to expire.

  • IAuth::STATUS_SUSPEND

    After a user login it'll set a session/cookie for suspend time of login. If there is no request for a while, it'll change status to suspend.

(They are available under Sim\Auth\Interfaces namespace)

isLoggedIn(): bool

Check if a user is logged in or not.

isExpired(): bool

Check if a user's session/cookie is expired or not.

isSuspended(): bool

Check if a user's session/cookie is suspended or not.

isNone(): bool

Check if there is no status (if status is none or not).

extendSuspendTime()

If it need to extend suspend time to start over the time, this method helps.

setExpiration($timestamp)

Set expiration time for user's login.

Note: Just enter the amount of time you need from now in seconds like 300.

Note: Also can pass string time like [+5 days]

getExpiration(): int

Get login expiration time.

Note: Default expiration time is 31536000 seconds that is 1 year.

setSuspendTime($timestamp)

Set suspend time for user's login.

Note: Just enter the amount of time you need from now in seconds like 300.

Note: Also can pass string time like [+5 days]

getSuspendTime(): int

Get login suspend time.

Note: Default suspend time is 1800 seconds that is 30 minutes.

setStorageType(int $type)

Set storage type of storing data. Storage types are explained above.

getStorageType(): int

Get storage type for storing data.

Note: Default storage type is IAuth::STORAGE_DB.

setNamespace(string $namespace)

Set namespace to separate your logic.

getNamespace(): string

Get current namespace.

Note: Default namespace is default.

loginWithID(int $id)

Make a login with user's id.

resume()

If there were a login before, it resumes that login.

logout()

Make user logout and delete and destroy any stored data.

getSessionUUID($username = null): array

Get a user's UUIDs.

Note: It only works with [IAuth::STORAGE_DB] otherwise it'll not work.

Note: You can pass user's username or id to get UUIDs or pass null or nothing to get current user's UUIDs.

destroySession(string $session_uuid): bool

Destroy a UUID session from database.

Note: It only works with [IAuth::STORAGE_DB] otherwise it'll not work.

getCurrentUser(): ?array

Get current user's credentials.

isAllow($resource, int $permission, $username = null): bool

Check if a user is allow to have a permission to a specific resource or not.

Note: You can pass resource's name or id.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

Permissions are one of below:

  • IAuth::PERMISSION_CREATE

  • IAuth::PERMISSION_READ

  • IAuth::PERMISSION_UPDATE

  • IAuth::PERMISSION_DELETE

(They are available under Sim\Auth\Interfaces namespace)

allowUser($resource, array $permission, $username = null)

Make a user to allow a resource with a specific permission.

Note: You can pass resource's name or id.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

disallowUser($resource, array $permission, $username = null)

Make a user to disallow a resource with a specific permission.

Note: You can pass resource's name or id.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

getUserRole($username): array

Get a user's roles.

Note: You can pass user's username or id.

getCurrentUserRole(): array

Get current user's roles.

userHasRole($role, $username = null): bool

Check user has a specific role

Note: You can pass user's username or id.

addRoleToUser(array $role, $username = null)

Add some roles to a user.

Note: You should pass an array of roles' name.

Note: You can pass user's username or id to add role to or pass null or nothing to add roles to current user.

isAdmin($username = null): bool

Check if a user is admin or not.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

quoteSingleName(string $name): string

Quote a column or table name or anything that needed to quote.

Note: It is tested with mysql but not with sql server.

Usage of each connection

DBAuth

Contains all of AbstractAuth and below extra information.

// this is constructor
$auth = new DBAuth (
    PDO $pdo_instance,
    string $namespace = 'default',
    array $crypt_keys = [],
    $algo = PASSWORD_BCRYPT,
    int $storage_type = IAuth::STORAGE_DB,
    ?array $config = null
);

$pdo_instance: See constructor of AbstractAuth.

$namespace: See constructor of AbstractAuth.

$crypt_keys: See constructor of AbstractAuth.

$algo: Algorithm to verify the password. It could be one of PASSWORD_DEFAULT or PASSWORD_BCRYPT to verify with password_verify function or other strings to verify with hash function.

$storage_type: See constructor of AbstractAuth.

$config: See constructor of AbstractAuth.

login(array $credentials, string $extra_query = null, array $bind_values = [])

$credentials should be like below structure:

// keys MUST be same as below
[
  'username' => provided username,
  'password' => provided password,
]

If there are more conditions to check for a user, you can pass them by $extra_query parameter and it MUST be parameterized.

Note: You should know that the login will check inside of a joined tables of users and roles. So if there is a condition, it's better to have the table's name before any column.

Note: You should quote your columns by yourself or use quoteSingleName that explained above.

// for example check if user is activated
$auth->login([
    'username' => provided username,
    'password' => provided password,
], 'users.is_active=:active', [
    'active' => 1,
]);

// to see if login has succeed
$isLoggedIn = $auth->isLoggedIn();

Note: To get the error of login, put it in try, catch block.

try {
    $auth->login([
        'username' => provided username,
        'password' => provided password,
    ]);
} catch (\Sim\Auth\Exceptions\IncorrectPasswordException $e) {
    // do something according to error
    // eg.
    echo 'Username or password is incorrect!';
} catch (\Sim\Auth\Exceptions\InvalidUserException $e) {
    // do something according to error
    // eg.
    echo 'Username or password is incorrect!';
} catch (\Sim\Auth\Interfaces\IDBException $e) {
    // do something according to error
    // eg.
    echo 'Failed database connection!';
}

loginWithUsername(string $username, string $extra_query = null, array $bind_values = [])

If there are more conditions to check for a user, you can pass them by $extra_query parameter and it MUST be parameterized.

Note: You should know that the login will check inside of a joined tables of users and roles. So if there is a condition, it's better to have the table's name before any column.

Note: You should quote your columns by yourself or use quoteSingleName that explained before.

// for example check if user is activated
$auth->login(
  'provided username',
  'users.is_active=:active', 
  [
    'active' => 1,
  ]
);

// to see if login has succeed
$isLoggedIn = $auth->isLoggedIn();

Note: To get the error of login, put it in try, catch block.

try {
    $auth->login('provided username');
} catch (\Sim\Auth\Exceptions\InvalidUserException $e) {
    // do something according to error
    // eg.
    echo 'Username or password is incorrect!';
} catch (\Sim\Auth\Interfaces\IDBException $e) {
    // do something according to error
    // eg.
    echo 'Failed database connection!';
}

AbstractAPIAuth

Contains all of AbstractBaseAuth.

__construct(
    PDO $pdo_instance,
    ?array $config = null
);

$pdo_instance: See constructor of AbstractBaseAuth.

$config: See constructor of AbstractBaseAuth.

isAllow($resource, int $permission, $username = null): bool

Check if a user is allow to have a permission to a specific resource or not.

Note: You can pass resource's name or id.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

Permissions are one of below:

  • IAuth::PERMISSION_CREATE

  • IAuth::PERMISSION_READ

  • IAuth::PERMISSION_UPDATE

  • IAuth::PERMISSION_DELETE

(They are available under Sim\Auth\Interfaces namespace)

getUserRole($username): array

Get a user's roles.

Note: You can pass user's username or id.

getCurrentUserRole(): array

Get current user's roles.

addRoleToUser(array $roles, $username = null)

Add some roles to a user.

Note: You should pass an array of roles' name.

Note: You can pass user's username or id to add role to or pass null or nothing to add roles to current user.

isAdmin($username = null): bool

Check if a user is admin or not.

Note: You can pass user's username or id to check or pass null or nothing to check for current user.

APIAuth

Contains all of AbstractAPIAuth and below extra information.

validate(array $credentials, string $extra_query = null, array $bind_values = []): bool

$credentials should be like below structure:

// keys MUST be same as below
[
  'username' => provided username,
  'api_key' => provided api key,
]

If there are more conditions to check for a user, you can pass them by $extra_query parameter and it MUST be parameterized.

Note: You should know that the verify will check inside of a joined tables of api_keys and roles. So if there is a condition, it's better to have the table's name before any column.

Note: You should quote your columns by yourself or use quoteSingleName that explained before.

// for example check if user is allowed or something
$auth->validate([
    'username' => provided username,
    'api_key' => provided api key,
], 'api_keys.allowed=:allow', [
    'allow' => 1,
]);

Note: To get the error of validateAPI, put it in try, catch block.

try {
    $auth->validate([
        'username' => provided username,
        'api_key' => provided api key,
    ]);
} catch (\Sim\Auth\Exceptions\IncorrectAPIKeyException $e) {
    // do something according to error
    // eg.
    echo 'Username or api key is incorrect!';
} catch (\Sim\Auth\Interfaces\IDBException $e) {
    // do something according to error
    // eg.
    echo 'Failed database connection!';
}

validateAPI(string $api_key, string $extra_query = null, array $bind_values = []): bool

If there are more conditions to check for a user, you can pass them by $extra_query parameter and it MUST be parameterized.

Note: You should know that the verify will check inside of a joined tables of api_keys and roles. So if there is a condition, it's better to have the table's name before any column.

Note: You should quote your columns by yourself or use quoteSingleName that explained before.

// for example check if user is allowed or something
$auth->validateAPI('provided api key', 'api_keys.allowed=:allow', [
    'allow' => 1,
]);

Note: To get the error of validate, put it in try, catch block.

try {
    $auth->validateAPI('provided api key');
} catch (\Sim\Auth\Exceptions\IncorrectAPIKeyException $e) {
    // do something according to error
    // eg.
    echo 'Api key is invalid!';
} catch (\Sim\Auth\Interfaces\IDBException $e) {
    // do something according to error
    // eg.
    echo 'Failed database connection!';
}

Dependencies

There is some dependencies here including:

Crypt library. With this feature, if any session/cookie hijacking happens, they can't see actual data because it is encrypted.

Cookie library to manipulate cookies.

Session library to manipulate sessions.

Agent library to get user's device and browser information to store in sessions table

And some dependency from Agent library.

License

Under MIT license.