Laravel101: A Step-by-Step Guide to Implementing a Simple Authentication System
Tags: laravel, php, laravel101, tutorial
Published: 2023-08-14 05:48:09

In this tutorial, we will walk you through the process of implementing a robust authentication system manually within your application.

In the Laravel ecosystem, there are numerous approaches to implementing authentication, ranging from session-based to API-based, and from lightweight packages like [Sanctum](https://laravel.com/docs/10.x/sanctum) to to more complex [Passport](https://laravel.com/docs/10.x/passport). Additionally, there are handy starter kits such as [Breeze](https://github.com/laravel/breeze) and [Jetstream](https://jetstream.laravel.com/introduction.html), which provide a streamlined starting point. However, for the purpose of this beginner tutorial, we will focus on implementing a straightforward authentication system that is as simple as possible.

---

Let’s begin by registering a user.

To create our controller in a separate directory, use the following command:
```sh
php artisan make:controller Auth/RegisterController
```
Inside of this controller we need two function. One function is responsible for displaying the view, while the other handles the controller’s logic.

Following `REST` architecture, we need a create method to display the registration view and a store method to handle the registration request:
```php
class RegisterController extends Controller
{
    public function create()
    {
        return view('auth.register');
    }

    public function store()
    {
    }
}
```
Then I have developed a simple user form that includes fields for name, email, and password input:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kja9ptnyzl85j9hypofy.png)


This form has just a simple layout `layout.auth` without our navbar. Additionally, I have put all the authentication forms into separate directory `resources/views/auth`

Now, let’s return to our routes and define the route for user registration.

```php
Route::get("/register", [RegisterController::class, 'create'])->name('register');
Route::post("/register", [RegisterController::class, 'store'])->name('register.store');
```

Next, we need to create a request which could look like bellow:

```php
class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => 'required|min:3',
            'email' => "required|email|unique:users,eamil",
            'password' => "required|min:4|max:20"
        ];
    }
}
```

The `email` rule is in place to ensure that the email address provided follows the proper email format. The `unique` rule is used to make sure that the email entered has not been used before.

No we can simply use it in our store function:

```php
public function store(RegisterRequest $request)
{
    $user = User::create($request->validated());

    Auth::login($user);

    return redirect("/tasks");
}
```
Here, I make use of an awesome class called `Auth` to provide a simple login within created user. Additionally, there is another globally defined helper function for Auth like `auth()`, which you can use: `auth()->login($user)`

Now, let’s have some fun. Go back to the tasks store function and try to retrieve the currently logged-in user in the application. To do this, we can use the `dd` :

```php
Auth::user()

// or 
auth()->user()

// or 

$request->user()
```

Here’s how you can do it:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn4z4s53cx259ut6eain.png)

Amazing! This means that our user now logged in in thew application successfully!

The another thing you need to pay attention here is the mass assignment issue when using the create function for a model.

If you look at the User model class created with Laravel, it handles this issue by default:
```php
protected $fillable = [
    'name',
    'email',
    'password',
];

protected $hidden = [
    'password',
    'remember_token',
];

protected $casts = [
    'email_verified_at' => 'datetime',
    'password' => 'hashed',
];
```
As you can see in the code, the password is converted to a hash by casting. This is a secure and useful way to store passwords in the database.

Now, What if you want to use your own hash method in a different way? Well, you can do it inside our request class. Just before passing the data, remember that all the processes occur after validation, and it just happens by rewriting validated method:
```php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Hash;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => "required|min:3",
            'email' => "required|email|unique:users,eamil",
            'password' => "required|min:4|max:20"
        ];
    }

    public function validated($key = null, $default = null)
    {
        return array_merge($this->validator->validated(), [
            'password' => Hash::make(request('password'))
        ]);
    }
}
```
The Laravel Hash facade offers secure hashing using Bcrypt and Argon2 algorithms. For detailed instructions you can refer to the official Laravel [documentation](https://laravel.com/docs/10.x/hashing).

Now, let’s return to our registry controller and bring everything together:
```php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterRequest;
use App\Models\User;

class RegisterController extends Controller
{
    public function create()
    {
        return view('auth.register');
    }

    public function store(RegisterRequest $request)
    {
        $user = User::create($request->validated());

        auth()->login($user);

        return redirect("/tasks");
    }
}
```
It’s really clear, but in the real world, after a user is created, we typically send a verification email to the user. However, for now, let’s keep things simple and cover that in the next lessons. Okay, now let’s get started by moving on to the login controller:
```sh
php artisan make:controller Auth/LoginController
```
Then let’s add routes :
```php
Route::get('login', [LoginController::class, 'create'])->name('login');
Route::post('login', [LoginController::class, 'store'])->name('login.store');
```
Just like in the register controller, we need to create a function in the login controller to display our login view, which I’ve kept simple from register form:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmll1h11ah9oymjd71f3.png)

And here is our `LoginRequest`
```php
class LoginRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email' => 'required|email',
            'password' => 'required'
        ];
    }
}
```
Finally let’s back to our login controller.

When it comes to creating a login system there are a few steps we need to follow. First, we check if the user exists in our database. Then, we verify if the entered password matches the hashed password stored in our records. If everything checks out and the user exists, we generate a session for them and grant access to the application.

You can handle all of the scenario by yourself, use another helper function called `attempt` in `Auth` facade that takes care of all these tasks. It’s quite simple to use. We just need to pass the user’s credentials and it handles the rest:
```php
class LoginController extends Controller
{
    public function create()
    {
        return view('auth.login');
    }

    public function store(LoginRequest $request)
    {
        if (auth()->attempt($request->validated())) {
            return redirect()->intended('/tasks');
        }

        return back();
    }
}
```
You can simply use the above code but if you want to handle exceptions and display error messages, you can make use of the helper function called `withErrors`. Just modify the code as follow:
```php
public function store(LoginRequest $request)
{
    if (auth()->attempt($request->validated())) {
        return redirect()->intended('/tasks');
    }

    return back()
        ->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ]);
}
```
Sure! If you’d like to go back to the view with the email value pre-filled, you can use the `withInput` function. Here's how you can do it:
```php
return back()
    ->withInput($request->only('email'))
    ->withErrors([
        'email' => 'The provided credentials do not match our records.',
    ]);
```

And this is the result:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nl6w4g75kqpq3mkfq70v.png)

Well, our next task is to finalize the authentication process and focus on implementing the logout feature. To begin, we’ll create a controller:
```sh
php artisan make:controller Auth/LogoutController
```
And ensure its routes are properly defined:
```php
Route::post('logout', LogoutController::class)->name('logout');
```
Since we have only one function in controller, I suggest utilizing an invocable method:
```php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;

class LogoutController extends Controller
{
    public function __invoke()
    {
        auth()->logout();

        return redirect('/login');
    }
}
```
That’s really easy! Now, let’s create our logout view. It’s a simple form with a button that you can click to submit the logout request:
```php
<form method="POST" action={{ route('logout') }}>
    @csrf
    <button type="submit">Logout</button>
</form>
```
How can we use this view in our application? and when should we avoid using it?

Currently, the navbar just displays options for signing in and signing up. We need to display a logout view when the user is logged in and this these options when the user is unauthorized.

To achieve this, we have two functions in blade. The first one auth is used to display content exclusively for authenticated users, while the second one guest is specifically for guest users or users who haven’t been authorized yet:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nedrz7w6ekb50wkv1t4m.png)


That’s it!

One important point is that we don’t want guests to have access to the task resources. Additionally, we want the logout request to be accessible only to authorized users.

In the upcoming lessons, we will explore middleware in Laravel in detail. But for now, let’s briefly mention that it consists of rules that check the current request before it reaches the intended endpoint. In our case, we check if the current request is made by an authorized user, and if not, we want to return a 403 exception.

To implement this, we can use the `auth` middleware. Laravel provides a function to add middleware specifically to a route. Here’s an example:
```php
Route::post('logout', LogoutController::class)->middleware('auth');
```
Additionally, we can group multiple routes together and apply the same middleware to all of them. Here’s an example:
```php
Route::group(['middleware' => 'auth'], function () {
    // Add your protected routes here
});
```
Now, let’s move on to our web routes and add both the logout and tasks resources to a group:
```php
Route::group(['middleware' => 'auth'], function () {
    Route::resource('tasks', TaskController::class);
    Route::post('logout', LogoutController::class)->name('logout');
});
```
Great job! We have successfully implemented the authorization system in Laravel, and it was really straightforward and clear. While we can add many more features to it, let’s keep it simple for now.

You can find out the project code of this series tutorial in the [repo](https://github.com/kazemmdev/laravel101).

Happy coding!