raid/authentication

Raid Authentication Package

dev-main 2024-04-26 13:29 UTC

This package is auto-updated.

Last update: 2024-04-26 13:29:53 UTC


README

This package is a wrapper for Laravel Sanctum package. it offers a new concepts to control authentication such as:

Installation

composer require raid/authentication

Configuration

Copy the config file to your own project by running the following command

php artisan vendor:publish --provider="Raid\Core\Authentication\Providers\AuthenticationServiceProvider"

Usage

Let's see basic usage for authentication with this package.

class LoginController extends Controller
{
    public function __invoke(Request $request, UserAuthenticator $authenticator)
    {
        $channel = $authenticator->attempt($request->only([
            'email', 'password',
        ]));

        return response()->json([
            'channel' => $channel->getName(),
            'token' => $channel->getStringToken(),
            'resource' => $channel->getAuthenticatable(),
            'errors' => $channel->errors()->toArray(),
        ]);
    }
}

The Authenticator will handle the authentication process and return a Channel instance that will contain the authentication information.

The Authenticator class defines the Authenticates class that will be used to find the user, also it defines the Channels that can be used to authenticate the user.

The Channel class depends on Workers to find the authenticated user, Then it can run some Rules and Steps to fulfill the authentication process.

Let's start digging into the Authenticates, Authenticators and Channels classes.

Authenticates

The Authenticates class will be used to find the user, and return Illuminate\Contracts\Auth\Authenticatable instance if found.

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as IlluminateUser;
use Raid\Core\Authentication\Authenticates\Contracts\Authenticates;

class User extends IlluminateUser implements Authenticates
{
    use HasApiTokens;

    public function findForAuthentication(string $attribute, mixed $value): ?Authenticatable
    {
        return $this->where($attribute, $value)->first();
    }
}

The Authenticates class must implement Authenticates interface.

The Authenticates class must define the findForAuthentication method.

The findForAuthentication method accepts two parameters: $attribute and $value passed from the given credentials.

The findForAuthentication method must return Illuminate\Contracts\Auth\Authenticatable instance if found.

Authenticator

The Authenticator class will be used to define the Authenticates class and Channels to process authentication with different channels.

You can use this command to create a new authenticator class

php artisan raid:make-authenticator UserAuthenticator

This will output the following code

<?php

namespace App\Http\Authentication\Authenticators;

use Raid\Core\Authentication\Authenticators\Authenticator;
use Raid\Core\Authentication\Authenticators\Contracts\AuthenticatorInterface;

class UserAuthenticator extends Authenticator implements AuthenticatorInterface
{
    public const NAME = '';

    protected string $authenticates = '';

    protected array $channels = [];
}

Let's configure the Authenticator class.

<?php

namespace App\Http\Authentication\Authenticators;

use App\Models\User;
use App\Http\Authentication\Channels\SystemChannel;
use Raid\Core\Authentication\Authenticators\Authenticator;
use Raid\Core\Authentication\Authenticators\Contracts\AuthenticatorInterface;

class UserAuthenticator extends Authenticator implements AuthenticatorInterface
{
    public const NAME = 'user';

    protected string $authenticates = User::class;

    protected array $channels = [
        SystemChannel::class,
    ];
}

The Authenticator class must implement AuthenticatorInterface.

The Authenticator class must extend the Authenticator class.

The Authenticator class should define the name constant.

The Authenticator class must define the authenticates property.

The Authenticator class should define the channels property.

The Authenticator class can handle authentication with any of its defined Channels.

You can define the channels with two ways:

  • channels property
  • config\authentication.php file
<?php

use App\Http\Authentication\Authenticators\UserAuthenticator;
use App\Http\Authentication\Channels\SystemChannel;

return [

    'authenticator_channels' => [
        UserAuthenticator::class => [
            SystemChannel::class,
        ],
    ],
];

This definition allows you to authenticate users with different Channels using channel name.

If you didn't pass any channel, the default channel will be used.

<?php

class LoginController extends Controller
{
    public function __invoke(Request $request, UserAuthenticator $authenticator)
    {
        $credentials = $request->only([
            'email', 'password',
        ]);
        
        $channel = $authenticator->attempt($credentials, 'system');
    }
}

Channel

The Channel class will be used to handle authentication process using the passed Authenticates class and Credentials.

You can use this command to create a new channel class

php artisan raid:make-channel SystemChannel

This will output the following code

<?php

namespace App\Http\Authentication\Channels;

use Raid\Core\Authentication\Channels\Channel;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;

class SystemChannel extends Channel implements ChannelInterface
{
    public const NAME = '';
}

Let's configure the Channel class.

<?php

namespace App\Http\Authentication\Channels;

use Raid\Core\Authentication\Channels\Channel;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;

class SystemChannel extends Channel implements ChannelInterface
{
    public const NAME = 'system';
}

The Channel class must implement ChannelInterface.

The Channel class must extend the Channel class.

The Channel class should define the name constant.

The Channel works through Workers to find the authenticated user, It matches the defined Workers attribute with the given credentials.

<?php

namespace App\Http\Authentication\Channels;

use App\Http\Authentication\Workers\EmailWorker;
use App\Http\Authentication\Workers\PhoneWorker;
use Raid\Core\Authentication\Channels\Channel;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;

class SystemChannel extends Channel implements ChannelInterface
{
    public const NAME = 'system';
    
    protected array $workers = [
        EmailWorker::class,
        PhoneWorker::class,
    ];
}

You can define the workers with two ways:

  • workers property
  • config\authentication.php file
<?php

use App\Http\Authentication\Channels\SystemChannel;
use App\Http\Authentication\Workers\EmailWorker;
use App\Http\Authentication\Workers\PhoneWorker;

return [

    'channel_workers' => [
        SystemChannel::class => [
            EmailWorker::class,
            PhoneWorker::class,
        ],
    ],
];

This definition allows you to authenticate users with different Workers using worker defined attribute.

Worker

The Worker class will be used to find the authenticated user based on the given credentials.

You can use this command to create a new worker class

php artisan raid:make-worker PhoneWorker

This will output the following code

<?php

namespace App\Http\Authentication\Workers;

use Raid\Core\Authentication\Workers\Worker;
use Raid\Core\Authentication\Workers\Contracts\WorkerInterface;

class PhoneWorker extends Worker implements WorkerInterface
{
    public const ATTRIBUTE = '';
}

Let's configure the Worker class.

<?php

namespace App\Http\Authentication\Workers;

use Raid\Core\Authentication\Workers\Worker;
use Raid\Core\Authentication\Workers\Contracts\WorkerInterface;

class PhoneWorker extends Worker implements WorkerInterface
{
    public const ATTRIBUTE = 'phone';
}

The Worker class must implement WorkerInterface.

The Worker class must extend the Worker class.

The Worker class must define the attribute constant.

The Worker can also define a QUERY_ATTRIBUTE constant to find the user.

The Attribute is used to match the Worker with the given credentials.

The Query Attribute is passed to the findForAuthentication method to find the user, if not defined, it will use the Attribute constant instead.

<?php

namespace App\Http\Authentication\Workers;

use Raid\Core\Authentication\Workers\Worker;
use Raid\Core\Authentication\Workers\Contracts\WorkerInterface;

class PhoneWorker extends Worker implements WorkerInterface
{
    public const ATTRIBUTE = 'phone';

    public const QUERY_ATTRIBUTE = 'phone_number';
}

Rule

The Rule class will be used to validate the authentication.

To apply Rules you need to implement ShouldRunRules interface to the Channel, Then you can define your Rules.

<?php

namespace App\Http\Authentication\Channels;

use App\Http\Authentication\Rules\VerifiedRule;
use Raid\Core\Authentication\Channels\Channel;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Channels\Contracts\ShouldRunRules;

class SystemChannel extends Channel implements ChannelInterface, ShouldRunRules
{
    public const NAME = 'system';
    
    protected array $rules = [
        VerifiedRule::class,    
    ];
}

You can define the rules with two ways:

  • rules property
  • config\authentication.php file
use App\Http\Authentication\Channels\SystemChannel;
use App\Http\Authentication\Rules\VerifiedRule;

return [
   
    'channel_rules' => [
        SystemChannel::class => [
            VerifiedRule::class,
      ],
];

The Rules will be applied to the Channel to validate the authentication.

You can use this command to create a new rule class

php artisan raid:make-rule VerifiedRule

This will output the following code

<?php

namespace App\Http\Authentication\Rules;

use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Rules\Contracts\RuleInterface;

class VerifiedRule implements RuleInterface
{
    public function handle(ChannelInterface $channel): bool
    {
    }

    public function fail(ChannelInterface $channel): void
    {
    }
}

Let's configure the Rule class.

<?php

namespace App\Http\Authentication\Rules;

use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Rules\Contracts\RuleInterface;

class VerifiedRule implements RuleInterface
{
    public function handle(ChannelInterface $channel): bool
    {
        return $channel->getAuthenticatable()->isVerified();
    }
    
    public function fail(ChannelInterface $channel): void
    {
        $channel->fail(message: __('auth.unverified'));
    }
}

The Rule class must implement RuleInterface.

The Rule class must define the handle method.

The handle method must return a boolean value.

The handle method will be called by the Channel to validate the authentication.

Step

The Step class will be used to add additional steps to the authentication process.

To apply Steps you need to implement ShouldRunSteps interface to the Channel, Then you can define your Steps.

<?php

namespace App\Http\Authentication\Channels;

use App\Http\Authentication\Steps\TwoFactorEmailStep;
use Raid\Core\Authentication\Channels\Channel;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Channels\Contracts\ShouldRunSteps;

class SystemChannel extends Channel implements ChannelInterface, ShouldRunSteps
{
    public const NAME = 'system';
    
    protected array $steps = [
        TwoFactorEmailStep::class,
    ],
}

You can define the steps with two ways:

  • steps property
  • config\authentication.php file
<?php

use App\Http\Authentication\Channels\SystemChannel;
use App\Http\Authentication\Steps\TwoFactorEmailStep;

return [
   
    'channel_steps' => [
        SystemChannel::class => [
            TwoFactorEmailStep::class,
      ],
];

The Steps will be applied to the Channel to add additional steps to the authentication process.

You can use this command to create a new step class

php artisan raid:make-step TwoFactorEmailStep

This will output the following code

<?php

namespace App\Http\Authentication\Steps;

use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Steps\Contracts\StepInterface;

class TwoFactorEmailStep implements StepInterface
{
    public function handle(ChannelInterface $channel): void
    {
    }
}

Let's configure the Step class.

<?php

namespace App\Http\Authentication\Steps;

use App\Core\Integrations\Mail\MailService;
use App\Mail\TwoFactorMail;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Steps\Contracts\StepInterface;

class TwoFactorEmailStep implements StepInterface
{
    public function __construct(
        private readonly MailService $mailService,
    ) {

    }

    public function handle(ChannelInterface $channel): void
    {
        $code = generate_code();
        
        $authenticatable = $channel->getAuthenticatable();

        $authenticatable->update([
            'two_factor_email_code' => $code,
        ]);
        
        $this->send(
            $authenticatable->getAttribute('email'),
            $authenticatable->getAttribute('name'),
            $code,
        );
    }

    private function send(string $email, string $name, int $code): void
    {
        $this->mailService->send(
            $email,
            new TwoFactorMail($name, $code),
        );
    }
}

The Step must implement StepInterface.

The Step class must define the handle method.

The handle method will be called by the Channel to add additional steps to the authentication process.

hint: Running any steps means that the Channel will stop the authentication process without issuing any tokens, This approach can be used in Multi-Factor authentication.

You can configure your step class to work through queues.

<?php

namespace App\Http\Authentication\Steps;

use App\Mail\TwoFactorMail;
use App\Core\Integrations\Mail\MailService;
use Raid\Core\Authentication\Channels\Contracts\ChannelInterface;
use Raid\Core\Authentication\Steps\Contracts\StepInterface;
use Raid\Core\Authentication\Steps\Contracts\ShouldRunQueue;

class TwoFactorEmailStep implements StepInterface, ShouldRunQueue
{
    use HasQueue;

    public function __construct(
        private readonly MailService $mailService,
    ) {

    }

    public function handle(ChannelInterface $channel): void
    {
        $code = generate_code();
        
        $authenticatable = $channel->getAuthenticatable();

        $authenticatable->update([
            'two_factor_email_code' => $code,
        ]);
        
        $this->send(
            $authenticatable->getAttribute('email'),
            $authenticatable->getAttribute('name'),
            $code,
        );
    }

    private function send(string $email, string $name, int $code): void
    {
        $this->mailService->send(
            $email,
            new TwoFactorMail($name, $code),
        );
    }
}

The queue step must implement ShouldRunQueue

The ShouldRunQueue class must define the queue method.

You can use the HasQueue trait to define the queue method with its default configuration.

You can override the trait queue configurations by defining these methods:

protected function getJob(): string
{
    // return your Job class;
}

protected function getConnection(): ?string
{
    // return your Connection name;
}

protected function getQueue(): ?string
{
    // return your Queue name;
}

protected function getDelay(): DateInterval|DateTimeInterface|int|null
{
    // return your Delay interval;
}

Channel Errors

You can use the Channel class to handle authentication errors through errors method.

You can add errors to channel using these methods:

$channel->errors()->add('key', 'message');
// or
$channel->fail('key', 'message');

You can check the channel errors using these methods:

$hasErrors = $channel->failed();
//or
$hasErrors = $channel->errors()->any();

$hasError = $channel->errors()->has('key');

$errorsByKey = $channel->errors()->get('key');

$firstError = $channel->errors()->first();

$lastError = $channel->errors()->last();

$errorsAsArray = $channel->errors()->toArray();

$errorsAsJson = $channel->errors()->toJson();

And that's it.

License

The MIT License (MIT). Please see License File for more information.

Credits

Security

If you discover any security-related issues, please email instead of using the issue tracker.

About Raid

Raid is a PHP framework created by Mohamed Khedr, and it is maintained by Mohamed Khedr.

Support Raid

Raid is an MIT-licensed open-source project. It's an independent project with its ongoing development made possible.