Laravel 9 provides a built-in authentication system for securing your application. There's always a restricted area in your application that requires you to grant access to allocated users. To do so, you need to identify the user trying to access this area and check whether they're allowed access. This is called Authentication and is a general component in Laravel and web applications on the internet.

Once you've identified the user and allowed access, you need to check what actions this user can perform. For instance, perhaps the user can create new records but not delete existing ones. This is also called Authorization and is a standard practice in Laravel and most web applications.

Authentication in Laravel

How does Laravel handle authentication and authorization? The answer is in this first article in a series on building MVC applications in Laravel.

Laravel uses guards to determine how a user is authenticated for each request. Guards essentially serve as the gatekeeper for your application, granting or denying access accordingly. Laravel provides several built-in guards, including.

  • Session Guard: This guard uses session data to authenticate users. A session is created when a user logs in and their session ID is stored in a cookie. On subsequent requests, the session guard retrieves the session ID from the cookie, retrieves the corresponding session data, and uses it to authenticate the user.
  • Token Guard: This guard uses API tokens to authenticate users. Instead of storing user data in a session, a token guard stores a unique API token in a database table and sends it to the client in an HTTP header. On subsequent requests, the token guard retrieves the token from the header to authenticate the user.

With both guards, the user provides the credentials as an email address and password.

In the Session Guard, Laravel matches the provided credentials with the records stored in the database. If there's a match, it stores the User ID inside the Session Data. The next time the user visits the application, Laravel checks whether an Auth Cookie holds a Session ID. If one is found, Laravel extracts the User ID from the Session Data and authenticates the user accordingly.

In the Token Guard, Laravel matches the provided credentials with the records stored in the database. Laravel generates a token representing the authenticated user's session if a match exists. This token is usually stored in a JSON Web Token (JWT) and contains the following information:

  • iss (Issuer): Identifies the issuing entity of the token, usually the name or identifier of the Laravel application.
  • sub (Subject): Identifies the token's subject, which is the user who's being authenticated. This field typically contains an identifier for the user, such as their unique ID in the database.
  • iat (Issued At): When the token was issued.
  • exp (Expiration Time): When the token expires.

In addition to these standard fields, you can add custom claims to the token to store additional information about the user, such as their permissions, roles, etc.

Laravel then returns this token to the front-end. It can store the token inside Local Storage or a cookie (recommended). On subsequent requests to the server, the client passes the JWT in the HTTP headers, and the server uses the information in the JWT to identify and authenticate the user for that request.

Login, Logout, and Auth Middleware

In this section, let's explore how Laravel handles authenticating users using the User model, Login View, Login Controller, Logout Controller, Routes, and Auth middleware.

I started by creating a new Laravel app. There are several methods for creating a new Laravel app. I chose the Docker-based one using the Laravel Sail package. You can read more about Laravel installation by following this URL (https://laravel.com/docs/10.x/installation).

Choose the method that best suits you. Before you start, make sure you have Docker running on your computer.

I'm using a MacBook Pro. I start by running this command:

curl -s \
"https://laravel.build/laravel-authentication"\ 
| bash

This creates a new Laravel application on your computer under the directory named laravel-authentication.

After the installer finishes, run the following commands:

  1. Build up the Docker container.
./vendor/bin/sail up
  1. Install the NPM packages.
./vendor/bin/sail npm install
  1. Serve the application.
./vendor/bin/sail run dev

The application is now accessible at http://localhost. Open the URL in the browser, and you'll see the same view as in Figure 1.

Figure 1: The Laravel 9 landing page
Figure 1: The Laravel 9 landing page

Next, let's install the Laravel Breeze starter kit. The Laravel team provides this starter kit for scaffolding Laravel authentication and Profile management. This is my ultimate choice when starting a new Laravel project. Trust me: It saves you a lot of time! You can read more about Laravel Breeze here (https://laravel.com/docs/10.x/starter-kits#laravel-breeze).

Laravel Breeze Installation

Laravel Breeze comes in four flavors:

  • Breeze & Blade
  • Breeze & React
  • Breeze & Vue
  • Breeze & Next.js/API

I'll use Breeze and Vue for this article.

Run the following commands to install Laravel Breeze, VueJS, and InertiaJS together.

./vendor/bin/sail composer require \laravel/breeze -dev
./vendor/bin/sail artisan breeze:install vue
./vendor/bin/sail artisan migrate
./vendor/bin/sail npm install
./vendor/bin/sail npm run dev

By installing Laravel Breeze, you've installed a ton of new things in your application. Let me list them briefly, but you can always check this repository commit titled (install Laravel Breeze package) to check every new file and configuration added by the package.

In addition to Breeze Starter Kit, Laravel also offers the JetStream Starter Kit, which is a more advanced option.

More importantly, Laravel Breeze does the following:

  1. Adds Vue, InertiaJS, and Tailwindcss
  2. Configures InertiaJS
  3. Configures Tailwindcss
  4. Scaffolds a front-end application using Vue/InertiaJS
  5. Adds Laravel Authentication controllers, views, and routes:
    1. Login
    2. Logout
    3. Register new user
    4. Forgot password
    5. Reset password
    6. Email verification
    7. Profile management
    8. Auth routes

Let's explore each of these sections in more detail.

Users in Laravel

Laravel represents a user in the application through the User model (app\Models\User.php). It backs up this model in the database by storing user information inside the users' database table. Figure 2. Shows the users' table structure.

Figure 2: The users' table structure
Figure 2: The users' table structure

When I first created the Laravel application, it automatically added a database/factories/UserFactory.php. Laravel uses the factory class to generate test data for the application. Listing 1 has the entire UserFactory.php file.

Listing 1: UserFactory.php file

<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    public function definition()
    {
        return [
             'name' => fake()->name(),
             'email' => fake()->unique()->safeEmail(),
             'email_verified_at' => now(),
             'password' => '$2y$10.../igi',
             'remember_token' => Str::random(10),
        ];
    }
    public function unverified()
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

Notice how Laravel hashes the password and never stores plain text passwords. When the user tries to log in, it hashes the incoming password and compares it to the one stored in the database.

In Laravel, the Factory and Seeder classes work together to generate test data for your application.

  • Factory: A factory class defines the data structure you want to generate. You can specify the attributes and values for each field in the model. The factory class uses Faker, a library for generating fake data, to fill in the values for each field.
  • Seeder: A seeder class calls the factory class and generates the data. You can specify how many records you want to create and what data type to generate. The seeder class inserts the data into the database.

Locate the database/seeders/DatabaseSeeder.php file, and inside the run() method, add the following line of code:

\App\Models\User::factory(1)->create();

This line inserts a new record into the users' database table.

Run this command to seed the database with a testing user record:

./vender/bin/sail db:seed

You can use factories and seeders to feed any testing data you want in your application. Generally, it's a best practice to provide factories and seeders to test your application locally.

User Registration

Laravel Breeze places the Register view inside the resources/js/Pages/Auth/Register.vue Vue component. Listing 2 shows a simplified version of the Register.vue component.

Listing 2: Register.vue component

<script setup>
const form = useForm({
    name: '',
    email: '',
    password: '',
    password_confirmation: '',
    terms: false,
});

const submit = () => {
    form.post(route('register'), {
        onFinish: () => form.reset(
            'password', 
            'password_confirmation'
        ),
    });
};
</script>

<template>
<form @submit.prevent="submit">
  <div>
     <TextInput id="name"
                type="text"
                v-model="form.name"
                required />
     <InputError :message="form.errors.name" />
  </div>

  <div>
     <TextInput id="email"
                type="email"
                v-model="form.email"
                required />
     <InputError :message="form.errors.email" />
  </div>

  <div>
     <TextInput  id="password"
                 type="password"
                 v-model="form.password"
                 required/>
     <InputError :message="form.errors.password" />
  </div>

  <div>
     <TextInput id="password_confirmation"
                type="password"
                v-model="form.password_confirmation"
                required />
     <InputError :message="form.errors.password_confirmation" />
  </div>

  <div>
     <PrimaryButton>
         Register
     </PrimaryButton>
  </div>
</form>
</template>

The script section uses the useForm composable that InertiaJS offers to create a form object with five fields: name, email, password, password_confirmation, and terms.

The submit function is then defined to submit the form data to the server. The form is posted to the Register POST route with the form.post() method. The onFinish() option is set, which specifies a function to be executed after the request has finished. In this case, the function form.reset('password', 'password_confirmation') is called, which resets the password and password_confirmation fields in the form.

The template section defines the Register form. It's defined with a submit.prevent() event listener, which prevents the default form submission behavior and calls the submit function instead. The form contains five input fields bound to a corresponding field in the form state object using the v-model directive. The ID attribute specifies a unique identifier for each field. Finally, the PrimaryButton component displays a button for submitting the form.

Let's check the Register routes Laravel Breeze defines inside the routes/auth.php file.

Route::get('register', [RegisteredUserController::class, 'create']
          )->name('register');

Route::post('register', [RegisteredUserController::class, 'store']);

The first route is a GET route for the /register URL, and it maps to the create() method of the RegisteredUserController class. The name() method is used to specify a name for the route, which can be used later in the application to generate URLs or to refer to the route by name.

The second route is a POST route for the /register URL, and it maps to the store() method of the RegisteredUserController class. This route is intended to receive form submissions from the login form.

Laravel Breeze stores all Auth routes in a separate file located at routes/auth.php

Listing 3 shows the source code for the store() method Laravel Breeze defines on the RegisteredUserController class. This code authenticates the user into the application.

Listing 3: RegisteredUserController store() method

public function store(Request $request): RedirectResponse
{
     $request->validate([
        'name' => 'required|string|max:255',
        'email' => [
            'required',
            'string',
            'email',
            'max:255',
            'unique:'.User::class
        ],
        'password' => [
            'required', 
            'confirmed', 
            Rules\Password::defaults()
        ],
     ]);
    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);
    event(new Registered($user));
    Auth::login($user);
    return redirect(RouteServiceProvider::HOME);
}

The store() method takes a Request object as an argument, which contains the user input data. The method first validates the user input data using the validate() method on the $request object. The validation rules specify that the name field is required and must be a string with a maximum length of 255 characters. The email field is also required. It must be a string, must be a valid email address, and must be unique in the users' table. The password field is required and must be confirmed. The Confirmed validation rule expects a matching field of password_confirmation to exist in the $request object. Finally, the Rules\Password::defaults() method is used to specify default validation rules for password fields in Laravel.

You can read more about Laravel Validation here (https://laravel.com/docs/10.x/validation).

If the validation passes, a new user is created by calling the create method on the User model and passing an array of data that includes the user's name, email, and hashed password. The Hash::make() method is used to hash the password.

An event Registered is then fired with the newly created user as its argument. Locate the app\Providers\EventServiceProvider.php file and notice the $listen variable.

protected $listen = [
    Registered::class => [SendEmailVerificationNotification::class,],
];

Laravel adds an event listener for the Registered event. The SendEmailVerificationNotification class sends an email verification to the registered user if the email verification feature is enabled. I'll look at the email verification feature in the coming sections.

Back to the store() method, the Auth::login() method is called to log the user in, and finally, the method returns a redirect to the home page using the redirect method and passing the RouteServiceProvider::HOME constant as its argument.

User Login

Laravel Breeze places the Login view inside the resources/js/Pages/Auth/Login.vue Vue component. Listing 4 shows a simplified version of the Login.vue component.

Listing 4: Login.vue component

<script setup>
const form = useForm({
    email: '',
    password: '',
    remember: false,
});

const submit = () => {
    form.post(route('login'), {
        onFinish: () => form.reset('password'),
    });
};
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <TextInput
                id="email"
                type="email"
                v-model="form.email"
                required
            />
            <InputError :message="form.errors.email" />
        </div>

        <div>
            <TextInput
                id="password"
                type="password"
                v-model="form.password"
                required
            />
            <InputError :message="form.errors.password" />
        </div>
      
        <div>
            <Checkbox name="remember" v-model:checked="form.remember" />
            <span class="ml-2 text-sm text-gray-600">Remember me</span>
        </div>

        <PrimaryButton>
            Log in
        </PrimaryButton>
    </form>
</template>

The script section uses the useForm composable that InertiaJS offers to create a form object with the properties email, password, and remember me. The properties are initialized with the default values of an empty string, an empty string, and a false string, respectively.

The submit function is then defined to submit the form data to the server. The form is posted to the Login POST route with the form.post() method and the onFinish() option used to reset the password field after completing the form submission.

The template section defines the Login form's structure, which comprises text inputs for the email and password fields, an error message component for displaying validation errors, a checkbox for remembering the user, and a primary button for submitting the form. The form is bound to the submit event using the @submit.prevent Vue directive, and the submit function defined in the script section is called when the form is submitted. The text inputs and the checkbox are bound to the properties of the form object using the v-model Vue directive. The error message components display the error messages from the form object using the message prop.

Let's check the Login routes Laravel Breeze defines inside the routes/auth.php file.

Route::get('login', [AuthenticatedSessionController::class, 'create']
)->name('login');

Route::post('login', [AuthenticatedSessionController::class, 'store');

The first route is a GET route for the /login URL, and it maps to the create() method of the AuthenticatedSessionController class. The name() method is used to specify a name for the route, which can be used later in the application to generate URLs or to refer to the route by name.

The second route is a POST route for the /login URL, and it maps to the store() method of the AuthenticatedSessionController class. This route is intended to receive form submissions from the login form.

Listing 5 shows the source code for the store() method Laravel Breeze defines on the AuthenticatedSessionController class. This code authenticates the user into the application.

Listing 5: AuthenticatedSessionController store() method

public function store(LoginRequest $request): 
    RedirectResponse
{
    $request->authenticate();
    $request->session()->regenerate();
    return redirect()->intended(RouteServiceProvider::HOME);
}

The store() method takes a LoginRequest FormRequest object as an argument and returns a RedirectResponse object. The method starts by calling the authenticate() method on the LoginRequest object, which is used to validate the incoming login request and verify that the user-provided credentials are valid.

The next line calls the regenerate() method on the session object, which regenerates the user's session ID to increase the security of the session.

Finally, the method returns a redirect response to the intended destination. Laravel tracks the intended destination when the user tries to visit a private page before logging in to the application. If there is no such intended destination, Laravel redirects the user to the URL specified in the constant RouteServicePovider::HOME, which is, in this case, the /dashboard URL.

Let's inspect the LoginRequest::authentiate() method closely. Listing 6 shows the entire source code for this method.

Listing 6: LoginRequest::authenticate() method

public function authenticate(): void
{   
    $this->ensureIsNotRateLimited();
    if (
        !Auth::attempt(
            $this->only('email', 'password'), 
            $this->boolean('remember')
        )
    ) {
         RateLimiter::hit($this->throttleKey());
         throw ValidationException::withMessages(
             ['email' => trans('auth.failed'),]);
    }
    RateLimiter::clear($this->throttleKey());
}

The authenticate() function accepts no parameters and returns nothing (void method).

The first line calls the ensureIsNotRateLimited() method, which checks if the user is rate limited (e.g., if they've tried to log in too many times in a short period). If the user is rate limited, the method throws an exception.

The second line uses the Auth facade's attempt() method to attempt to log the user in with the email and password provided in the request. The method also accepts a Boolean argument, determining whether the user's session should be remembered.

If the authentication attempt fails, the method calls the hit() method on the RateLimiter class to increase the user's failed attempts. The method then throws a ValidationException with a message indicating that the authentication has failed.

If the authentication attempt is successful, the method calls the clear() method on the RateLimiter class to clear the number of failed attempts for the user.

That's how Laravel Breeze validates the user's credentials and attempts a login to the application.

You must have noticed that the attempt() method accepts as a third parameter whether it should remember the user's login session. If Laravel resolves this parameter to a Boolean True value, it generates a remember-me token.

In Laravel, the remember token is a random string generated and stored in the database when the user checks the “Remember Me” checkbox during login. This token is then used to identify the user on subsequent visits.

Typically, the remember me token is a hash of a combination of the user's ID, a random value, and the current timestamp. This hash is designed to be unique and secure, so the resulting hash will be different even if two users have the same combination of ID and timestamp. The hashed token is then stored in the database, linked to the user's account, and used to identify the user on subsequent visits.

Generating a remember token involves the following steps:

  1. When a user logs into the application and selects the “Remember me” option, Laravel generates a unique token using a secure random number generator.
  2. The token is combined with additional information, such as the user's ID and a time stamp, to create a signed token that the application can verify.
  3. The signed token is stored in the user's session, cookie, and database record. This allows the application to maintain the user's login state and easily verify the user's authentication status on subsequent requests.

In this way, the remember token serves as a secure and convenient way for Laravel to remember the user's identity and allow them to remain logged in across multiple visits.

This section summarizes the entire process that Laravel uses to log users into the application.

Email Verification

Laravel supports email verification at the core. Email verification is a process you use to confirm the validity and authenticity of an email address provided by a user during registration.

To enable email verification in your application, ensure that the User model implements the Illuminate\Contracts\Auth\MustVerifyEmail contract. That's all!

Assuming that you've enabled email verification in your application, right after a successful registration process, Laravel fires the Registered event, as you've seen in the “User Registration” section previously.

Laravel hooks into the Registered event inside the AppServiceProvider::class by listening to this event and fires the SendEmailVerificationNotification event listener.

protected $listen = [
    Registered::class => [SendEmailVerificationNotification::class, ],
];

Inside this listener, Laravel sends the user a verification email to the email address provided. The email contains a URL generated by URL::temporarySignedRoute() method with a default expiration date of 60 seconds and a hashed string of the User ID and User Email.

The user must click this URL, and the Laravel application validates and checks the parameters and verifies the user in the database.

Now that you know how the email verification process works, let's discover all aspects of enabling and making this feature usable in Laravel.

Start by navigating to the app/Models/User.php model file. The User model must implement the MustVerifyEmail contract as follows:

class User extends 
      Authenticatable implements MustVerifyEmail

Next, let's revisit the SendEmailVerificationNotification::class and see how Laravel prepares and sends the verification email. Listing 7 shows the class implementation.

Listing 7: SendEmailNotification class

class SendEmailVerificationNotification
{
     public function handle(Registered $event)
     {
         if ($event->user instanceof MustVerifyEmail && 
               !$event->user->hasVerifiedEmail()
         ) 
         {$event->user->sendEmailVerificationNotification(); }
     }
}

The handle() method of the class sends the user an email only when:

  • The User model implements the MustVerifyEmail contract.
  • The user is not yet verified.

Let's inspect the sendEmailVerificationNotification() method Laravel defines inside the Illuminate\Auth\MustVerifyEmail.php trait. Listing 8 shows the related code in this trait.

Listing 8: MustVerifyEmail trait

<?php
namespace Illuminate\Auth;
use Illuminate\Auth\Notifications\VerifyEmail;
trait MustVerifyEmail
{
    // ...
    public function sendEmailVerificationNotification()
    {
        $this->notify(new VerifyEmail);
    }
    // ...
}

Laravel applies this trait on the User model. Hence, the sendEmailVerificationNotification() method is accessible on the User model instance.

Laravel issues a new notification using the VerifyEmail notification class inside the method. Once again, because Laravel uses the Notifiable trait on the User model, it's safe to call the $this->notify() method to send out a notification.

The VerifyEmail::class is just a Laravel notification found in the Illuminate\Auth\Notifications\VerifyEmail.php file.

I'll dedicate a full article to using Laravel Notifications in the future. For now, it's enough to understand that this VerifyEmail notification prepares an email with the temporary signed route and sends it to the user. Listing 9 shows the code to prepare the signed route.

Listing 9: Temporary signed route code

URL::temporarySignedRoute('verification.verify', Carbon::now()->addMinutes(
    Config::get('auth.verification.expire', 60)
),
   [
       'id' => $notifiable->getKey(),
       'hash' => sha1($notifiable->getEmailForVerification()),
   ]
);

The method takes three parameters:

  • The name of the route, which is verification.verify
  • The expiration time for the signed route, which is set to the current time plus the number of minutes specified in the auth.verification.expire configuration setting (with a default value of 60 minutes)
  • An array of parameters passed to the route. In this case, the array contains two parameters: id, the User ID, and hash, is a hash of the user's email address.

This is an example of a temporary signed route in Laravel:

email/3/374a976d6e8288d2633ed8ad94b6d4bdd8487d50?expirs=1676289142
&signature=b2fd6e7f2def3e2cfeb9aedab9b58c840e8263a20975bc85d1ecdc78d6441d3

Figure 3 shows the email sent to the user to verify the email address.

Figure 3: The email address verification email in Laravel
Figure 3: The email address verification email in Laravel

You can read more about the signed URLs in Laravel here (https://laravel.com/docs/10.x/urls#signed-urls).

Email verification is an optional feature. It's recommended to use it, though.

Once the user clicks the Verify Email Address button, Laravel loads the route verification.verify in the browser. This is handled by the VerifyEmailController::class __invoke() method, as shown in Listing 10.

Listing 10: VerifyEmailController __invoke() method

class VerifyEmailController extends Controller
{
    public function __invoke(EmailVerificationRequest $request): RedirectResponse
    {
        if ($request->user()->hasVerifiedEmail()) 
        {
            return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
        }
        if ($request->user()->markEmailAsVerified()) 
        {
            event(new Verified($request->user()));
        }
        return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
    }
}

The code in Listing 10 checks if the user is already verified otherwise, it verifies the user by calling the markEmailAsVerified() method and finally redirects the user to the intended route, if it exists; otherwise, it redirects the user to the route defined in the RouteServiceProvider::HOME constant.

Laravel provides the \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class middleware defined inside the app/Http/Kernel.php file under the route middleware array. You can use this route middleware to prevent access to the application (even if the user has successfully signed in) until the user verifies the email address. Here's an example of limiting users to verified only users when accessing the /dashboard URL in the application:

Route::get('/dashboard', function () {
    return Inertia::render('Dashboard')
})->middleware(['verified'])->name('dashboard');

The user has to verify the email address before accessing the /dashboard URL. Listing 11 shows a simplified version of the source code for the EnsureEmailIsVerified::class.

Listing 11: EnsureEmailsVerified middleware class

class EnsureEmailIsVerified
{
    public function handle(
        $request, 
        Closure $next, 
        $redirectToRoute = null
    )
    {
        if (! $request->user() ||
            ($request->user() instanceof MustVerifyEmail &&
                ! $request->user()->hasVerifiedEmail())) {
                    return $request->Redirect::guest(
                         URL::route($redirectToRoute ?: 'verification.notice')
                    );
                }
                return $next($request);
    }
}

The middleware redirects the user to the verification.notice route only when the user has not yet verified the email address. Otherwise, it passes over the request to the next middleware in the middleware pipes.

To get more insight on the topic of Laravel middleware and how it handles the request flow, you can grab a free copy of my e-book on this topic: “Mastering Laravel's Request Flow” (https://tinyurl.com/2pbddgf5).

“Master Laravel's Request Flow” delves deep inside the Laravel Framework and explains all needed details.

Laravel defines the verification.notice route in the routes/auth.php file as follows:

Route::get('verify-email', 
   [
       EmailVerificationPromptController::class, '__invoke'
   ]
)->name('verification.notice');

The __invoke() method defined on the EmailVerificationPromptController::class redirects the user to the resources/js/Pages/Auth/VerifyEmail.vue page. The page allows the user to resend the verification email, as shown in Figure 4.

Figure 4: Resend the email address verification email
Figure 4: Resend the email address verification email

That's all you need to know, at this stage, regarding email verification in Laravel. Let's move on and study how Password Reset works in Laravel.

Forgot Password and Reset

On the Login page, you can retrieve your forgotten password by clicking the link named Forgot your Password?

Laravel uses the following markup to define this link:

<Link :href="route('password.request')">
    Forgot your password?
</Link>

You can find the route password.request inside the routes/auth.php file defined as follows:

Route::get('forgot-password', 
    [PasswordResetLinkController::class, 'create']
)->name('password.request');

Let's explore the create() method.

public function create(): Response
{
  return Inertia::render('Auth/ForgotPassword', [
      'status' => session('status'),
    ]);
}

The create() method returns the resources/js/Pages/Auth/ForgotPassword.vue page. Listing 12 shows a simplified version of the ForgotPassword.vue page.

Listing 12: ForgotPassword.vue page

<script setup>
defineProps({status: String,});
const form = useForm({email: '',});
const submit = () => {form.post(route('password.email'));};
</script>
<template>
    <div v-if="status">
        {{ status }}
    </div>
    <form @submit.prevent="submit">
        <div>
            <TextInput
                id="email"
                type="email"
                v-model="form.email"
                required
            />
        </div>
        <PrimaryButton>
            Email Password Reset Link
        </PrimaryButton>
    </form>
</template>

The <script> section defines the component's logic and data.

  • defineProps() is a function that defines the component's props. In this case, the component has a single prop named status, which is of type String.
  • The useForm hook manages the component's form data. It creates an object named form with a single property email set to an empty string.
  • The submit() function is called when the form is submitted. It uses the form.post() method to send a POST request to the URL defined by the password.email route.

The <template> section defines the component's HTML structure and visual appearance.

  • A div to display the status of sending a Forgot password reset link.
  • The form element contains a single input field to allow the user to enter the email address. The v-model directive is used to bind the value of the input to the form.email property.
  • The PrimaryButton component is a button the user can click to submit the form and trigger the password reset process.

You can find the route password.email inside the routes/auth.php file defined as follows:

Route::post('forgot-password', 
    [PasswordResetLinkController::class, 'store']
)->name('password.email');

Let's explore the store() method. Listing 13 shows the source code for the store() method.

Listing 13: PasswordResetLinkController::class store() method

public function store(Request $request): RedirectResponse
{
    $request->validate(['email' => 'required|email', ]);
    $status = Password::sendResetLink($request->only('email'));
    if ($status == Password::RESET_LINK_SENT) 
    {
        return back()->with('status', __($status));
    }
    throw ValidationException::withMessages([
        'email' => [trans($status)],
    ]);
}

The function takes an instance of the Request class as its input. This class represents the HTTP request that the user submitted. The method validates the incoming email address to make sure it exists and that it's an email address.

The sendResetLink() method on the Password Facade class is called, passing the user's email address entered in the form. This method sends a password reset email to the user.

The result of the sendResetLink() method is stored in the $status variable. If $status equals Password::RESET_LINK_SENT, the password reset email was successfully sent, and the user is redirected to the previous page (ForgotPassword.vue) with a success status message.

If the value of $status is not equal to Password::RESET_LINK_SENT, an error occurs, and a ValidationException is thrown with an error message.

Digging more inside the Laravel framework, I discovered that the `Illuminate\Auth\Notifications\ResetPassword.php is the notification that executes when it's time to send the user a password reset link.

Let's track the most important part of this notification class, the protected resetUrl() function. Listing 14 shows the source code for the resetUrl() function.

Listing 14: ResetPassword::class resetUrl() function

protected function resetUrl($notifiable)
{
    // ...
    return url(route('password.reset', [
        'token' => $this->token,
        'email' => $notifiable->getEmailForPasswordReset(),
    ], false));
}

The method takes an instance of the $notifiable object as its input, which represents a recipient of the password reset email.

It returns a password reset URL generated by calling the url() function and passing it the result of the route function.

The route() function generates a URL for the password reset page represented by the password.reset route. It takes as parameters the token and the email address of the recipient. Laravel generates a random token and stores it in the database inside the password_resets table.

The password.reset route is defined inside the routes/auth.php file as follows:

Route::get('reset-password/{token}', 
    [NewPasswordController::class, 'create']
)->name('password.reset');

This route handles clicking the password reset link that was sent in the email. Let's explore the create() method. Listing 15 shows the source code for the NewPasswordController::class create() method.

Listing 15: NewPasswordController::class create() method

public function create(Request $request): Response
{
    return Inertia::render('Auth/ResetPassword', [
        'email' => $request->email,
        'token' => $request->route('token'),
    ]);
}

The method returns the resources/js/Pages/Auth/ResetPassword.vue page passing down to the page the email and token parameters. Listing 16 shows the ResetPassword.vue page.

Listing 16: ResetPassword.vue page

<script setup>

const props = defineProps({
    email: String,
    token: String,
});

const form = useForm({
    token: props.token,
    email: props.email,
    password: '',
    password_confirmation: '',
});

const submit = () => {
    form.post(route('password.store'), {
        onFinish: () => form.reset(
            'password', 
            'password_confirmation'
        ),
    });
};
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <TextInput
                id="email"
                type="email"
                v-model="form.email"
                required
            />
        </div>

        <div>
            <TextInput
                id="password"
                type="password"
                v-model="form.password"
                required
            />
        </div>

        <div>
             <TextInput
                 id="password_confirmation"
                 type="password"
                 v-model="form.password_confirmation"
                 required
             />
        </div>

       <PrimaryButton>
           Reset Password
       </PrimaryButton>
    </form>
</template>

The page prompts the user to submit their email, new password, and confirmed password. It then submits the form to the password.store route. Let's check this route in the routes/auth.php file.

Route::post('reset-password', 
    [NewPasswordController::class, 'store']
)->name('password.store');

This is the last step in retrieving and resetting the user password. Let's explore the store() method. Listing 17 shows a simplified store() method version on the NewPasswordController::class.

Listing 17: NewPasswordController::class store() method

public function store(Request $request): RedirectResponse
{
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => [
            'required', 
            'confirmed', 
            Rules\Password::defaults()
        ],
    ]);
    
    $status = Password::reset($request->only(
       'email', 
       'password', 
       'password_confirmation', 
       'token'
    ),

        function ($user) use ($request) {
            $user->forceFill([
                'password' => Hash::make($request->password),
                'remember_token' => Str::random(60),
            ])->save();
    
            event(new PasswordReset($user));
        }
    );
    if ($status == Password::PASSWORD_RESET) 
    {
        return redirect()->route('login')->with('status', __($status));
    }
    throw ValidationException::withMessages([
        'email' => [trans($status)],
    ]);
}

The store() method handles the form submission for resetting the password. The method first validates the incoming request using the validate() method, which ensures that the request contains a token, an email, and a password that matches the confirmation and meets the default password rules.

The Password::reset() method is then called to reset the user's password. It takes an array of parameters, including the user's email, the new password, the confirmation of the new password, and the reset token. It also takes a closure responsible for updating the user's password and firing an event to notify the system of the password reset upon a successful password reset by Laravel.

The reset() method matches the incoming token and email with those stored in the password_resets database table before authorizing a password reset action.

If the password reset is successful, the method redirects the user to the login page with a status message indicating that the password has been reset. If the password reset fails, a ValidationException is thrown with a message indicating the reason for the failure. Figure 5 shows the password reset page.

Figure 5: The password reset page
Figure 5: The password reset page

That's it! Now you can retrieve and reset your forgotten password in Laravel.

User Logout

Logging out of the Laravel application is straightforward. You'll notice a link to log out from the application on the Welcome page. This link posts to a route named logout.

Let's explore the logout route in the routes/auth.php file.

Route::post('logout', 
    [
        AuthenticatedSessionController::class, 'destroy'
   ]
)->name('logout');

The destroy() method defined on the AuthenticatedSessionController::class is responsible for logging the user out. Let's check it out. Listing 18 shows the destory() method.

Listing 18: AuthenticatedSessionController::class destory() method

public function destroy(Request $request): RedirectResponse
{
    Auth::guard('web')->logout();
    
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect('/');
}

The destroy() method handles the request to log out the authenticated user.

The Auth::guard('web')->logout() method is called to log out the user by invalidating the user's session on the web guard, which is the default authentication guard for web applications in Laravel (SessionGuard.php).

The session()->invalidate() method is called to invalidate the user's session, which ensures that any data associated with the user's session is cleared from the storage and generates a new session ID.

The session()->regenerateToken() method is called to regenerate the CSRF token, which is a security feature that helps to prevent session hijacking. I didn't cover the CSRF protection in Laravel as Laravel automatically handles it. However, if you're interested in learning more about it, you can read about it here: https://laravel.com/docs/10.x/csrf.

Finally, the method redirects the user to the home page. The user is now fully logged out of the application.

Auth Middleware and Protecting Routes

In the last article of the series of building MVC apps with PHP Laravel, I've explained routing and middleware in Laravel. If you need a quick refresh, here's the link: https://www.codemag.com/Article/2301041/Mastering-Routing-and-Middleware-in-PHP-Laravel.

In the context of today's article, let's look at the Authentication route middleware Laravel provides to ensure that an authenticated user can access a secure area in your application.

Let's start by limiting routes to only authenticated users. The routes/auth.php file defines a route for the /dashboard URL that limits it to only authenticated users.

Route::get('/dashboard', function() {
   return Inertia::render('Dashboard');
})->middleware(['auth'])->name('dashboard');

The route defines the auth middleware to protect the /dashboard URL. Where's this auth middleware defined? Go to the app/Http/Kernel.php file and look at the $routeMiddleware array.

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
];

The name auth is an alias for the authentication middleware at app/Http/Middleware/Authentication::class. Listing 19 shows the source code for the Authenticate::class middleware. The Authenticate::class middleware extends the Illuminate\Auth\Middleware\Authenticate::class middleware class provided by Laravel framework.

Listing 19: Authenticate middleware

<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return route('login');
        }
    }
}

The redirectTo() function is used internally by the Illuminate\Auth\Middleware\Authenticate::class to redirect the user to the /login page when authentication fails.

Let's have a look at the Illuminate\Auth\Middleware\Authenticate::class.

public function handle(
   $request, 
   Closure $next, 
   ...$guards)
   {
       $this->authenticate($request, $guards);
       return $next($request);
   }

The handle() function is straightforward. It tries to authenticate the user and then hands off the processing of the current request to the remaining middleware in the pipeline.

You can add your own logic to handle user authentication for other scenarios you might have in your application.

Conclusion

This article provided a comprehensive overview of Session authentication, which is widely used by numerous web applications in Laravel. However, authentication in Laravel extends beyond just Session authentication, as there are other providers, such as token-based authentication and authentication with social providers like Google and Twitter.

Subsequent installments of this series will explore token-based authentication, a popular method for authenticating API requests, and will also delve into authenticating with social providers.

I aim to equip you with the knowledge and skills to implement robust and secure Laravel applications.

Happy Laraveling!