implementing-password-based-authentication-manually-in-laravel

Implementing Password Based Authentication Manually In Laravel

We have seen above how we can use laravel’s default scaffoldings, It is fast and easy to implement and we can also modify many things in that, But if you want full control over your implementation, then we will see how you can implement an authentication system from scratch in laravel.

While we are going to implement an authentication system from scratch, we are still going to use laravel frameworks charm or there will be no benefit in using this framework.

We will need to create following things in order to make our auth system work

  1. Auth Guard
  2. Authentication Provider
  3. Eloquent model

Now that we know what we need, let’s start building it.

Creating Model to be used for authentication

First let’s create a user modal that we will use for authentication to communicate with the database.

Default model that is shipped with laravel will work fine with minor modification.

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    protected $primaryKey = 'email';
    // $incrementing should be set to false if your primary key is not set to auto incrementing
    public $incrementing = false;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

//  we have added getUser here so we have flexibility of changing column  //  name in future without modifying at other places
    public function getUser($username)
    {
        return $this->where('email', $username)->first();
    }

//    Uncomment this function to use your customer columns,
//    By default it will use your primary key defiled above.
//
//    public function getAuthIdentifier() {
//        return $this->attributes['email'];
//    }
//
//    public function getAuthPassword() {
//        return $this->attributes['password'];
//    }

If you want to create other modal then you can do so just like that, Just make sure that you extend “Illuminate\Foundation\Auth\User” That is also the base model for authentication, If you don’t extend that then we need to implement AuthenticatableContract, AuthorizableContract, CanResetPasswordContract & use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail in our model which will make things a little massy.

In Model we have added getUser function to get User Object based on provided user identifier, We have added this method in modal so we can change column name in future without affecting other auth code.

Creating User Provider

For this example we are going to make CustomerAuthProvider class to act as User Provider to be used in authentication, You can create this file anywhere inside the App folder based on your structure preference.

Our Customer User provider should implement UserProvider, UserProvider has many defined methods that can be implemented.

<?php

namespace App\Providers;


use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Facades\Hash;

class CustomerAuthProvider implements UserProvider
{

    private $model;

    public function __construct(\App\User $userModel)
    {
        $this->model = $userModel;
    }

    public function retrieveById($identifier)
    {
        return $this->model->getUser($identifier);
    }
    
    public function retrieveByToken($identifier, $token)
    {
        // We will not implement this as we are not dealing with password remember feature
    }
    
    public function updateRememberToken(Authenticatable $user, $token)
    {
        // We will not implement this as we are not dealing with password remember feature
    }
    
    public function retrieveByCredentials(array $credentials)
    {
        return $this->model->getUser($credentials['username']);
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        if (strcmp($credentials['username'], $user->getAuthIdentifier()) === 0) {
            if (Hash::check($credentials['password'], $user->getAuthPassword())) {
                return true;
            }
        }

        return false;
    }
}

This will work as it is, But you can also change function logic as per your need.

Creating Auth Guard

Now we will create our custom auth guard, we will name it CustomAuthGuard for this example. Just like our provider, you can create this file anywhere you want inside the App folder based on your structure preference.

Our guard must implement ‘Guard’, Guard also has many methods defined that we should implement in our guard.

<?php

namespace App;


use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;

class CustomAuthGuard implements Guard
{

    private $request;
    private $provider;
    private $user;

    public function __construct(UserProvider $provider, Request $request)
    {
        $this->request = $request;
        $this->provider = $provider;
        $this->user = null;
    }

    public function check()
    {
        return isset($this->user);
    }

    public function guest()
    {
        return !isset($this->user);
    }

    public function user()
    {
        if (isset($this->user)) {
            return $this->user;
        }
    }

    public function id()
    {
        if (isset($this->user)) {
            return $this->user->getAuthIdentifier();
        }
    }

    public function validate(array $credentials = [])
    {
        if (!isset($credentials['username']) || empty($credentials['username']) || !isset($credentials['password']) || empty($credentials['password'])) {
            return false;
        }

        $user = $this->provider->retrieveById($credentials['username']);

        if (!isset($user)) {
            return false;
        }

        if ($this->provider->validateCredentials($user, $credentials)) {
            $this->setUser($user);
            return true;
        } else {
            return false;
        }
    }

    public function setUser(Authenticatable $user)
    {
        $this->user = $user;
    }
}

Now, We have created our Modal, Provider & Guard. Only thing that is pending is to bind it in our AuthServiceProvider.

Add following line inside boot method in AuthServiceProvider

// add custom guard provider
Auth::provider('custom', function ($app, array $config) {
	return new CustomerAuthProvider($app->make('App\User'));
});

// add custom guard
Auth::extend('custom', function ($app, $name, array $config) {
	return new CustomAuthGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
});

So, It should look something like this

<?php

namespace App\Providers;

use App\CustomAuthGuard;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();

        // add custom guard provider
        Auth::provider('custom', function ($app, array $config) {
            return new CustomerAuthProvider($app->make('App\User'));
        });

        // add custom guard
        Auth::extend('custom', function ($app, $name, array $config) {
            return new CustomAuthGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
        });
    }
}

Now, We can use our newly created Auth Guard & Auth Provider in config/auth.php

<?php

return [
    
    'defaults' => [
        'guard' => 'custom',
        'passwords' => 'users',
    ],
    

    'guards' => [
        
        'custom' => [
            'driver' => 'custom',
            'provider' => 'custom',
        ],

    ],
    
    'providers' => [
        'custom' => [
            'driver' => 'custom'
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
    
    'password_timeout' => 10800,
];

As we have defined our provider with name ‘custom’ providers.custom.driver will be ‘custom’, Same we have added our guard with name ‘custom’ and so value for guards.custom.driver will be ‘custom’.

Now, Our Custom auth system is ready, Let’s check it out by creating a login function.

public function login(Request $request) {
	// By preparing our credentials like this, We separate View with our Auth logic.
	$credentials = [
		'username' => $request->get('email'),
		'password' => $request->get('password'),
	];

	if (Auth::validate($credentials)) {
		// We can get current authenticated user like this
		$user = Auth::user();
		// Or we can simply get username (User identifier) like this
		$username = Auth::id();

		echo 'Yes, We are logged in!';
	} else {
		echo 'Well, There is an issue with provided credentials :(';
	}
}

I am not going to discuss Frontend here, You can design it however you like and you can create controllers however you like, This is the main advantage of using a custom auth system that gives you this flexibility.

You now just have to define your routes for login & registration, Design your front end, Create controller and add above method with your custom logic, That’s all.

For Registration, You can use the following function to get started.

public function register(Request $request) {
	$this->validate($request, [
		'email' => 'required|string|email|unique:users|max:255',
		'name' => 'required|string|max:255',
		'password' => 'required|string|min:8|confirmed', // confirmed will check for xxx_confirmation, So your request should have password_confirmation for this to pass
	]);

	$user = User::create([
		'name' => $request->get('name'),
		'email' => $request->get('email'),
		'password' => Hash::make($request->get('password')),
	]);
	
	// Your logic goes here...
}

Leave a Reply

Your email address will not be published. Required fields are marked *