Have you seen our new video tutorials? Check it out!

Users Module

Full featured user authentication and authorization management.

Introduction

The Users module provides easy to use and powerful user management , authentication, and authorization.

Features

The users module comes with everything you need for simple and advanced authentication and authorization needs.

  • Registration
  • Authentication
  • Authorization
  • Password Reset
  • Login Throttling
  • Users & Roles Management
  • Addon based permission system.
  • Multiple activation scenarios.
  • Extension-based Authentication
  • Extension-based Security
  • Configurable Login Fields
  • Integrated with Laravel's Auth service.
  • Interface Design (implementations your own as needed).

Installation

You can install the Users module with the addon:install command:

php artisan addon:install anomaly.module.users
Notice: The Users module comes installed with PyroCMS out of the box.

Configuration

You can override Users module configuration by publishing the addon and modifying the resulting configuration file:

php artisan addon:publish anomaly.module.users

The addon will be published to /resources/{application}/addons/anomaly/users-module.

Login Field

The anomaly.module.users::config.login value determines which field is used for logging in along with the password. Valid options are email (default) or username.

'login' => env('LOGIN', 'email'),

You can also use the .env file to set this value with LOGIN.

Activation Mode

The anomaly.module.users::config.activation_mode value determines how users are activated when they register. A user must be activated in order to login.

'activation_mode' => env('ACTIVATION_MODE', 'email'),

Valid options are:

  • email - Send an activation email to the user. This is the default mode.
  • manual - Require an admin to manually activate the user.
  • automatic - Automatically activate the user when they register.

Usage

This section will show you how to use the addon via API and in the view layer.

Users

Users are extensible stream entries that can be associated with multiples roles. Users have their own permissions that merge with those inherited from the roles they belong to.

User Fields

Below is a list of fields in the users stream. Fields are accessed as attributes:

$user->email;

Same goes for decorated instances in Twig:

{{ user.email }}
Fields
Key Type Description

email

email

The login email address.

username

text

The login username.

password

text

The hashed login password.

roles

multiple relationship

The roles the user has.

display_name

text

The publicly displayable name.

first_name

text

The real first name.

last_name

text

The real last name.

permissions

textarea

The serialized user permission array.

last_login_at

datetime

The last login datetime.

last_activity_at

text

The datetime for the last action made by the user.

ip_address

text

The last IP address that accessed the user account.

Custom User Fields

Custom user fields assigned through the control panel are assigned directly to the users stream and can be accessed directly from the user object.

$user->favorite_color;

And in Twig:

{{ user.favorite_color }}

User Interface

This section will go over the features of the \Anomaly\UsersModule\User\Contract\UserInterface class.

UserInterface::hasRole()

The hasRole method ensures that the user has the given role.

Returns: boolean
Arguments
Key Required Type Default Description

$role

true

string

none

The role ID, slug, or interface.

Example
if (auth()->user()->hasRole('admin') {
    echo "User is an admin!";
}
Twig
{% if auth_user().hasRole('admin') %}
    User is an admin!
{% endif %}
UserInterface::hasAnyRole()

The hasAnyRole method ensures that the user has at least one of the provided roles.

Returns: boolean
Arguments
Key Required Type Default Description

$roles

true

mixed

none

An array of role IDs or slugs. A collection of roles can also be passed.

Example
if (auth()->user()->hasAnyRole(['admin', 'manager'])) {
    echo 'Hello ' . $user->display_name;
}
Twig
{% if auth().user().hasAnyRole(['admin', 'manager'])) %}
    Hello {{ auth_user().display_name }}
{% endif %}
UserInterface::isAdmin()

The isAdmin method returns if the user has the admin role or not.

Returns: boolean
Example
if ($user->isAdmin()) {
    echo "Hi Admin.";
}
Twig
Hello {{ auth_user().isAdmin() ? 'admin' : 'user' }}
UserInterface::hasPermission()

The hasPermission method verifies that the user has the permission.

Returns: boolean
Arguments
Key Required Type Default Description

$permission

true

string

none

The permission string.

$checkRoles

false

boolean

true

Check the users roles for the permission too.

Example
if (auth()->user()->hasPermission('vendor.module.example::example.test')) {
    // So something
}
Twig
{% if auth_user().hasPermission('vendor.module.example::example.test')) %}
    {# So something #}
{% endif %}
UserInterface::hasAnyPermission()

The hasAnyPermission method verifies that the user has at least one of the given permissions.

Returns: boolean
Arguments
Key Required Type Default Description

$permissions

true

array

none

The array of permissions.

$checkRoles

false

boolean

true

Check the users roles for the permission too.

Example
$hasPermission = auth()->user()->hasAnyPermission(
    ['vendor.module.example::example.test', 'vendor.module.example::widget.example']
);

if ($hasPermission) {
    // Do something
}
Twig
{% set hasPermission = auth_user().hasAnyPermission(
    ['vendor.module.example::example.test', 'vendor.module.example::widget.example']
) %}

{% if hasPermission %}
    {# Do something #}
{% endif %}

User Presenter

This section will go over the \Anomaly\UsersModule\User\UserPresenter class that's returned in the view layer.

UserPresenter::name()

The name method returns the concatenated first and last name.

Returns: string
Example
$decorated->name();
Twig
Hi {{ user().name() }}
UserPresenter::gravatar()

The gravatar method returns a Gravatar image URL for the user.

Returns: string
Arguments
Key Required Type Default Description

$parameters

true

array

none

Gravatar URL parameters.

Example
$decorated->avatar(['d' => 'mm']);
Twig
{{ img(user().gravatar({'d': 'mm'})).class('img-rounded')|raw }}

User Repository

The \Anomaly\UsersModule\User\Contract\UserRepositoryInterface class helps you retrieve users from the database.

UserRepositoryInterface::findByEmail()

The findByEmail method finds a user by their email.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or null
Arguments
Key Required Type Default Description

$email

true

string

none

The users email.

Example
$user = $repository->findByEmail('[email protected]');
UserRepositoryInterface::findByUsername()

The findByUsername method finds a user by their username.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or null
Arguments
Key Required Type Default Description

$username

true

string

none

The username of the user.

Example
$user = $repository->findByUsername('ryanthepyro');
UserRepositoryInterface::findByCredentials()

The findByCredentials method finds a user by their login field and password.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or null
Arguments
Key Required Type Default Description

$credentials

true

array

none

The credentials array containing email/username and password.

Example
$user = $repository->findByCredentials(['email' => '[email protected]', 'password' => 'secret password']);

Roles

Roles are groups of users that define what the users has access to via role permissions. Roles can also be used as an inclusive test like i.e. "Does this user have the foo role?".

Role Fields

Below is a list of fields in the roles stream. Fields are accessed as attributes:

$role->slug;

Same goes for decorated instances in Twig:

{{ role.slug }}
Fields
Key Type Description

name

text

The name of the role.

slug

slug

The slug used for API access.

description

textarea

A description for the role.

permissions

textarea

A serialized array of role permissions.

Role Interface

This section will go over the features of the \Anomaly\UsersModule\Role\Contract\RoleInterface class.

RoleInterface::hasPermission()

The hasPermission method verifies that the role has the permission.

Returns: boolean
Arguments
Key Required Type Default Description

$permission

true

string

none

The permission string.

Example
if ($role->hasPermission('vendor.module.example::example.test')) {
    // Do something
}
Twig
{% if role.hasPermission('vendor.module.example::example.test') %}
    {# Do something #}
{% endif %}
RoleInterface::hasAnyPermission()

The hasAnyPermission method verifies that the role has at least one of the given permissions.

Returns: boolean
Arguments
Key Required Type Default Description

$permissions

true

array

none

The array of permissions.

Example
$hasPermission = $role->hasAnyPermission(
    ['vendor.module.example::example.test', 'vendor.module.example::widget.example']
);

if ($hasPermission) {
    // Do something
}
Twig
{% set hasPermission = role.hasAnyPermission(
    ['vendor.module.example::example.test', 'vendor.module.example::widget.example']
) %}

{% if hasPermission %}
    {# Do something #}
{% endif %}

Role Repository

The \Anomaly\UsersModule\Role\Contract\RoleRepositoryInterface class helps you retrieve roles from the database.

RoleRepositoryInterface::allButAdmin()

The allButAdmin method returns all roles but the admin one.

Returns: \Anomaly\UsersModule\Role\RoleCollection
Example
$roles = $repository->allButAdmin();
RoleRepositoryInterface::findBySlug()

The findBySlug method returns a role by it's slug.

Returns: \Anomaly\UsersModule\Role\Contract\RoleInterface or null
Arguments
Key Required Type Default Description

$slug

true

string

none

The slug of the role.

Example
$guest = $repository->findBySlug('guest');
RoleRepositoryInterface::findByPermission()

The findByPermission method returns all roles with the permission.

Returns: \Anomaly\UsersModule\Role\RoleCollection
Arguments
Key Required Type Default Description

$permission

true

string

none

The permission string.

Example
$roles = $repository->findByPermission('example.module.test::example.test');

// Search for partial-match permissions.
$roles = $repository->findByPermission('example.module.test::*');
RoleRepositoryInterface::updatePermissions()

The updatePermissions method updates the permissions for a role.

Returns: \Anomaly\UsersModule\Role\Contract\RoleInterface
Arguments
Key Required Type Default Description

$role

true

object

none

The role instance.

$permissions

true

array

none

The array of role permissions.

Example
$repository->updatePermissions(
    $role,
    [
        'example.module.test::example.test',
        'example.module.test::example.foo'
    ]
);

Plugin

This section will go over how to use the plugin that comes with the Users module.

user

The user function returns a decorated user instance from the identifier provided.

Returns: \Anomaly\UsersModule\User\UserPresenter or null
Arguments
Key Required Type Default Description

$identifier

false

mixed

Will return the active user.

The id, email, or username of the user to return.

Twig
Hello {{ user().display_name }}

Sup {{ user('ryanthepyro').first_name }}

role

The role method returns a decorated role instance from the identifier provided.

Returns: \Anomaly\UsersModule\Role\RolePresenter or null
Arguments
Key Required Type Default Description

$identifier

true

mixed

none

The ID or slug of the role to return.

Example
{% if role('user').hasPermission('example.module.test::example.test') %}
    {# Do something #}
{% endif %}

Services

This section will introduce you to the various services available in the Users module and how to use them.

Authentication

This section will introduce you to the authentication service and how to user it.

User Authenticator

This class will go over the \Anomaly\UsersModule\User\UserAuthenticator class and how to use it.

UserAuthenticator::attempt()

The attempt method attempts to authorize a user. The login method is ran if the authentication succeeds.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or false
Arguments
Key Required Type Default Description

$credentials

true

array

none

The credentials array of email/username and password.

$remember

false

boolean

false

The "remember me" flag.

Example
$authenticator->attempt(['email' => '[email protected]', 'password' => 'secret']);
UserAuthenticator::authenticate()

The authenticate method authenticates credentials without logging the user in.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or false
Arguments
Key Required Type Default Description

$credentials

true

array

none

The credentials array of email/username and password.

Example
$authenticator->authenticate(['email' => '[email protected]', 'password' => 'secret password']);
UserAuthenticator::login()

The login method logs in the user.

Returns: void
Arguments
Key Required Type Default Description

$user

true

object

none

The user instance.

Example
$authenticator->login($user);
UserAuthenticator::logout()

The logout method logs out the user.

Returns: void
Arguments
Key Required Type Default Description

$user

true

object

none

The user to logout.

Example
$authenticator->logout($user);
UserAuthenticator::kickOut()

The kickOut method kicks a user. The kickOut method is similar to logout but a different event is fired for you to hook into as needed.

Returns: void
Arguments
Key Required Type Default Description

$user

true

object

none

The user to kick out.

Example
$authenticator->kickOut($user);

Security

This section will introduce you to the security checker and how to use it.

User Security

This section will introduce the \Anomaly\UsersModule\User\UserSecurity class and how to use it.

UserSecurity::attempt()

The attempt method runs the security checks when an authentication attempt is performed.

Returns: \Illuminate\Http\RedirectResponse or true
Example
$result = $security->attemp();
UserSecurity::check()

The check method verifies that a user passes all the security checks.

Returns: \Illuminate\Http\RedirectResponse or true
Arguments
Key Required Type Default Description

$user

false

object

none

The user instance to check.

Example
$result = $security->check($user);

Middleware

This section will introduce you to the middleware services and how to use them.

Authorizing Routes

The Users module load's middleware into the stack that allows you to set custom parameters that ensure the request is made by an authorized user.

Authorize By Role

You can authorize a route with \Anomaly\UsersModule\Http\Middleware\AuthorizeRouteRole by defining the anomaly.module.users::role route parameter;

'example/test' => [
    'anomaly.module.users::role' => 'my_role',
    'uses' => [email protected]'
]

You can also define an array of roles where the user must have at least one:

'example/test' => [
    'anomaly.module.users::role' => ['my_role', 'another_role'],
    'uses' => [email protected]'
]

Additionally you may include an optional redirect path and message in case the user does not pass authorization:

'example/test' => [
    'anomaly.module.users::role' => 'my_role',
    'anomaly.module.users::redirect' => '/',
    'anomaly.module.users::message' => 'Sorry, you do not have access.',
    'uses' => [email protected]'
]
Authorize By Permission

You can authorize a route with \Anomaly\UsersModule\Http\Middleware\AuthorizeRoutePermission by defining the anomaly.module.users::permission route parameter;

'example/test' => [
    'anomaly.module.users::permission' => 'vendor.module.example::widgets.test',
    'uses' => [email protected]'
]

You can also define an array of permissions where the user must have at least one:

'example/test' => [
    'anomaly.module.users::role' => ['vendor.module.example::widgets.test', 'vendor.module.example::widgets.example'],
    'uses' => [email protected]'
]

Additionally you may include an optional redirect path and message in case the user does not pass authorization:

'example/test' => [
    'anomaly.module.users::permission' => 'vendor.module.example::widgets.test',
    'anomaly.module.users::redirect' => '/',
    'anomaly.module.users::message' => 'Sorry, you do not have access.',
    'uses' => [email protected]'
]

Extensions

This section will go over the addon extensions and how they work.

Authenticators

Authenticators are responsible for authenticating credentials and login attempts.

Authenticator Extension

This section will go over the \Anomaly\UsersModule\User\Authenticator\AuthenticatorExtension class.

AuthenticatorExtension::authenticate()

The authenticate method is responsible for authenticating the credentials and returning null, a user, or a redirect.

Returns: \Anomaly\UsersModule\User\Contract\UserInterface or \Illuminate\Http\RedirectResponse or null
Arguments
Key Required Type Default Description

$credentials

true

array

none

The login information.

Writing Authenticators

This section will show you how to write your own custom authenticator extension.

Creating the extension

The first thing we need to do is to use the make:addon command to create our extension:

php artisan make:addon anomaly.extension.default_authenticator
Extending the authenticator extension

The extension you create must extend the \Anomaly\UsersModule\User\Authenticator\AuthenticatorExtension class:

<?php namespace Anomaly\DefaultAuthenticatorExtension;

use Anomaly\DefaultAuthenticatorExtension\Command\AuthenticateCredentials;
use Anomaly\UsersModule\User\Authenticator\AuthenticatorExtension;
use Anomaly\UsersModule\User\Contract\UserInterface;

class DefaultAuthenticatorExtension extends AuthenticatorExtension
{

    /**
     * This extensions provides a basic
     * authenticator for the users module.
     *
     * @var string
     */
    protected $provides = 'anomaly.module.users::authenticator.default';

    /**
     * Authenticate a set of credentials.
     *
     * @param array $credentials
     * @return null|UserInterface
     */
    public function authenticate(array $credentials)
    {
        return $this->dispatch(new AuthenticateCredentials($credentials));
    }
}

You must define the provides property as anomaly.module.users::authenticator.your_widget_slug so that it's picked up as a supported extension.

Authenticating credentials

The primary task of any authenticators is to authenticate a login request. In this example we will use a command thats dispatched within the authenticate method to check the credentials:

public function authenticate(array $credentials)
{
    return $this->dispatch(new AuthenticateCredentials($credentials));
}

Our AuthenticateCredentials command is responsible for the actual work:

<?php namespace Anomaly\DefaultAuthenticatorExtension\Command;

use Anomaly\UsersModule\User\Contract\UserRepositoryInterface;

class AuthenticateCredentials
{

    /**
     * The credentials to authenticate.
     *
     * @var array
     */
    protected $credentials;

    /**
     * Create a new AuthenticateCredentials instance.
     *
     * @param array $credentials
     */
    public function __construct(array $credentials)
    {
        $this->credentials = $credentials;
    }

    /**
     * Handle the command.
     *
     * @param  UserRepositoryInterface                               $users
     * @return \Anomaly\UsersModule\User\Contract\UserInterface|null
     */
    public function handle(UserRepositoryInterface $users)
    {
        if (!isset($this->credentials['password']) && !isset($this->credentials['email'])) {
            return null;
        }

        return $users->findByCredentials($this->credentials);
    }
}
Redirecting authentication requests

The authenticate method can return an instance of the user, null, or a redirect instance. In the case a redirect is returns the request will be redirected immediately. After the redirect is made the authentication will be in your hands!

Security Checks

Security checks are responsible for filtering login attempts and users. For example a security check could enforce that a certain criteria is met by the user. Or check that the login form is not being flooded.

Security Check Extension

This section will go over the \Anomaly\UsersModule\User\Security\Contract\SecurityCheckInterface class.

SecurityCheckExtension::attempt()

The attempt method is used to check security during a login attempt.

Returns: \Illuminate\Http\RedirectResponse or true
SecurityCheckExtension::check()

The check method is run during each request against a user.

Returns: \Illuminate\Http\RedirectResponse or true
Arguments
Key Required Type Default Description

$user

false

UserInterface

null

The user to check security for.

Writing Security Checks

This section will show you how to write your own custom security check extension.

Creating the extension

The first thing we need to do is to use the make:addon command to create our extension:

php artisan make:addon anomaly.extension.user_security_check
Extending the security check extension

The extension you create must extend the \Anomaly\UsersModule\User\Security\SecurityCheckExtension class:

<?php namespace Anomaly\UserSecurityCheckExtension;

use Anomaly\UserSecurityCheckExtension\Command\CheckUser;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Anomaly\UsersModule\User\Security\SecurityCheckExtension;
use Symfony\Component\HttpFoundation\Response;

class UserSecurityCheckExtension extends SecurityCheckExtension
{

    /**
     * This extension provides a security check that
     * assures the user is active, enabled, etc.
     *
     * @var null|string
     */
    protected $provides = 'anomaly.module.users::security_check.user';

    /**
     * Check an HTTP request.
     *
     * @param  UserInterface $user
     * @return bool|Response
     */
    public function check(UserInterface $user = null)
    {
        if (!$user) {
            return true;
        }

        return $this->dispatch(new CheckUser($user));
    }

}

You must define the provides property as anomaly.module.users::security_check.your_widget_slug so that it's picked up as a supported extension.

Validating security

The primary task of any security check is to validate a user. In this example we will use a command thats dispatched within the check method to check the user over and make sure they are valid and allowed:

public function check(UserInterface $user = null)
{
    if (!$user) {
        return true;
    }

    return $this->dispatch(new CheckUser($user));
}

Our CheckUser command is responsible for the actual work:

<?php namespace Anomaly\UserSecurityCheckExtension\Command;

use Anomaly\Streams\Platform\Message\MessageBag;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Anomaly\UsersModule\User\UserAuthenticator;
use Illuminate\Routing\Redirector;

class CheckUser
{

    /**
     * The user instance.
     *
     * @var UserInterface
     */
    protected $user;

    /**
     * Create a new CheckUser instance.
     *
     * @param UserInterface $user
     */
    public function __construct(UserInterface $user)
    {
        $this->user = $user;
    }

    /**
     * @param  UserAuthenticator                      $authenticator
     * @param  MessageBag                             $message
     * @param  Redirector                             $redirect
     * @return bool|\Illuminate\Http\RedirectResponse
     */
    public function handle(UserAuthenticator $authenticator, MessageBag $message, Redirector $redirect)
    {
        if (!$this->user->isActivated()) {

            $message->error('Your account has not been activated.');

            $authenticator->logout(); // Just in case.

            return $redirect->back();
        }

        if (!$this->user->isEnabled()) {

            $message->error('Your account has been disabled.');

            $authenticator->logout(); // Just in case.

            return $redirect->back();
        }

        return true;
    }
}