Classic Logic

Manual User Authentication in Laravel

Laravel comes with a flexible user authentication mechanism which I highly recommend. Occasionally though, you have a good reason not to use what comes with Laravel and instead roll your own user authentication mechanism. The Laravel documentation does a pretty-good job of documenting what I’m about to do. This post is just a concrete example.

This is roughly what I’ll be doing.

  1. Define my own table for managing users, via a migration.
  2. Create an eloquent model for the users
  3. Modify users provider to use our model for users
  4. Implement signup, login, and logout

Not that it matters, but I used sqlite3 database while doing this. Things should work exactly the same with Postgres and MySql.

Defining Customers Table

(You ought to do these before you run any migrations).

I want to define a customers table for storing user information. To keep things simple, I just want to keep track of the username and password, and no other information.

Why am I calling this table “customers” instead of “users”? So that you readers can be sure that the table I’m referring to is different from the built-in users table that comes with Laravel. You are free to name your table to store user information whatever you want.

Once you create a new Laravel project via laravel new my-project command, there will already be some migrations ready to run. One of these is (on my Laravel v8.78.1 project) 2014_10_12_000000_create_users_table.php which creates the default users table. It has fields for email, password etc. Since I won’t use this table, I am deleting the migration for it.

In our customers table, these are going to be the columns:

  • id – integer
  • username – unique string
  • password – string
  • timestamps

Go ahead and create a new migration to create this table:

php artisan make:migration create_customers_table

Here’s how my schema definition looked like:

Schema::create('customers', function (Blueprint $table) {
    $table->id();
    $table->string('username')->unique();
    $table->string('password');
    $table->timestamps();
});

Run the migrations using php artisan migrate.

Define Customer Model

Define an Eloquent model for the newly created customers table. This model should implement Illuminate\Contracts\Auth\Authenticatable interface which contains methods Laravel’s authentication system needs to use. For concrete implementation of the methods in that interface you may use the Illuminate\Auth\Authenticatable trait, unless you wish to implement those methods yourself.

<?php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model implements AuthenticatableContract
{
    use Authenticatable, HasFactory;
}

Use tinker to ensure that the Customer model got hooked up to the customers table:

$ php artisan tinker
Psy Shell v0.10.12 (PHP 8.0.9 — cli) by Justin Hileman
>>> use App\Models\Customer;
>>> Customer::get();
=> Illuminate\Database\Eloquent\Collection {#4311
     all: [],
   }
>>>

The collection is empty because no data has been written to the customers table. The important point is that there were no errors.

Modify User Provider

To paraphrase the official docs, the user provider defines how the users are actually retrieved out of the database.

In config/auth.php, there would already be an entry like the following:

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // ...
    ],

Modify this entry like so:

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\Customer::class,
        ],

        // ...
    ],

This tells Laravel to use the Customer model as the user model.

Implement Login, Logout, and Signup mechanism

Signup is trivial; store the username and hashed password into the database and you’re done. Hashing via Hash::make() works well with the login mechanism.

public function signup(Request $request)
{
    $credentials = $request->validate([
        'username' => 'required|string|unique:customers,username',
        'password' => 'required|string',
    ]);

    $customer = new Customer;
    $customer->username = $credentials['username'];
    $customer->password = Hash::make($credentials['password']);
    $customer->save();

    return redirect('auth/login')->with('flash', 'User registered!');
}

Login is a little bit more involved, but since the official documentation does an excellent job of explaining those and I have nothing additional to add, I refer to them here instead of copy-pasting the code.

A full working example of this manual authentication mechanism (including signup and login pages) is available on my Github repo here. I suggest you check it out to see a concrete example.