6 min read

Laravel From Zero to Hero 01: User Authentication

Contents

Prerequisite

Before we begin setting up our project, we need to ensure a webdriver is installed on our dev environment. We will be using Laravel Dusk for web interaction tests which will use a headless version of the Chrome browser. If you are using Homestead, you can go ahead and set “webdriver” to true and provision your server.

Learn more about Laravel Homestead or DDEV with Docker for local Laravel development.

When running through your provisioning script, it will download and install the Chrome driver ready to be used via Dusk.

Testing is very much an optional part of development, but it is highly recommended you try it out since many companies and developers are looking at TDD and testing as a requirement.

Creating the project

First you need to create our application. If you have Homestead running, then you can SSH into your box and cd into your code directory. Here you can run laravel new blog or you can use composer create-project --prefer-dist laravel/laravel blog if you don’t have the Laravel installer setup. Now you can open the project using your favourite editor.

Don’t forget to setup the database and point your server configuration to the new project.

Now you have your project setup, we can move on and create everything needed for user authentication including registering and logging in a user.

Backend Authentication

Creating your first PHPUnit tests

Next, we will be adding some tests for user authentication. You will need to run php artisan make:test UserLoginTest which will create UserLoginTest.php in your tests/Feature directory.

If you run ./vendor/bin/phpunit you can see how the command works. Your test will pass because of the boilerplate code, but you’ll be removing that and updating it with some real tests.

Delete the “testExample” method and replace it with this code…

use RefreshDatabase;    public function testUserLogsInSuccessfully()    {        $user = User::factory()->create([            'email' => 'test@test.com',            'password' => Hash::make('secret')        ]);        $response = $this->post('/login', [            'email' => 'test@test.com',            'password' => 'secret'        ]);        $response->assertRedirect('/home');        $this->assertTrue(Auth::check());        $this->assertTrue(Auth::user()->is($user));    }    public function testUserLogsInUnsuccessfully()    {        $user = User::factory()->create([            'email' => 'test@test.com',            'password' => Hash::make('secret')        ]);        $response = $this->post('/login', [            'email' => 'test@test.com',            'password' => 'incorrect-password'        ]);        $response->assertSessionHasErrors(['email']);        $this->assertFalse(Auth::check());    }

use RefreshDatabase; tells PHPUnit that in order to run our test, we need to have our tables setup and empty in order to continue.

Inside of testUserLogsInSuccessfully method, we create a user factory inside the database with the email “test@test.com” and password “secret“. The response is then sending a post request to “/login” with what will be the correct email and password. The test should then expect to receive a redirect to “/home“. The next assertion expects Auth::check() to be true and finally, the last assertion expects the correct authenticated user to match the user within the database and that will return true.

The testUserLogsInUnsuccessfully will create the same user, but we will purposely use the incorrect details and expect our response to return an error with our email address. It will then check the authentication has failed, which will be true.

Replace your use statements with this code

use App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase;use Illuminate\Foundation\Testing\WithFaker;use Illuminate\Support\Facades\Auth;use Illuminate\Support\Facades\Hash;use Tests\TestCase;

And now you can run ./vendor/bin/phpunit to run the test suite. Of course this will fail and you’ll see why soon.

First off, we need to add a database connection into our phpunit.xml file. Open it up and add the following before the closing </php> tag.

<server name="DB_CONNECTION" value="sqlite"/><server name="DB_DATABASE" value=":memory:"/>

When we run our tests, we will be using sqlite in memory. This means that a database will never be created on our system and once tests are finished, all the data will be cleared.

Laravel version 7 and 8

From Laravel version 7, all controller files within the auth directory have been removed and placed into laravel/ui. For the purposes of this course, we’re going to recreate these ourselves, however to begin we will need to enter the following composer require laravel/ui in order to get started.

Authentication Routes

First thing you need to do is add the authentication routes. Head over to routes/web.php and add…

<?phpAuth::routes();

Authentication Controllers

Next, you need to create the home controller file in app/Http/Controllers/HomeController.php or by running the artisan command php artisan make:controller HomeController if you run the latter command, then remove the code and add the folowing…

<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;class HomeController extends Controller{    /**     * Create a new controller instance.     *     * @return void     */    public function __construct()    {        $this->middleware('auth');    }    /**     * Show the application dashboard.     *     * @return \Illuminate\Contracts\Support\Renderable     */    public function index()    {        return view('home');    }}

Within the app/Http/Controllers directory we’ll create a new folder called Auth. Inside our Auth directory, we want to make the following controllers with the following code.

ConfirmPasswordController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;use Illuminate\Foundation\Auth\ConfirmsPasswords;class ConfirmPasswordController extends Controller{    /*    |--------------------------------------------------------------------------    | Confirm Password Controller    |--------------------------------------------------------------------------    |    | This controller is responsible for handling password confirmations and    | uses a simple trait to include the behavior. You're free to explore    | this trait and override any functions that require customization.    |    */    use ConfirmsPasswords;    /**     * Where to redirect users when the intended url fails.     *     * @var string     */    protected $redirectTo = '/home';    /**     * Create a new controller instance.     *     * @return void     */    public function __construct()    {        $this->middleware('auth');    }}

LoginController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;use Illuminate\Foundation\Auth\AuthenticatesUsers;class LoginController extends Controller{    /*    |--------------------------------------------------------------------------    | Login Controller    |--------------------------------------------------------------------------    |    | This controller handles authenticating users for the application and    | redirecting them to your home screen. The controller uses a trait    | to conveniently provide its functionality to your applications.    |    */    use AuthenticatesUsers;    /**     * Where to redirect users after login.     *     * @var string     */    protected $redirectTo = '/home';    /**     * Create a new controller instance.     *     * @return void     */    public function __construct()    {        $this->middleware('guest')->except('logout');    }}

RegisterController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;use App\User;use Illuminate\Foundation\Auth\RegistersUsers;use Illuminate\Support\Facades\Hash;use Illuminate\Support\Facades\Validator;class RegisterController extends Controller{    /*    |--------------------------------------------------------------------------    | Register Controller    |--------------------------------------------------------------------------    |    | This controller handles the registration of new users as well as their    | validation and creation. By default this controller uses a trait to    | provide this functionality without requiring any additional code.    |    */    use RegistersUsers;    /**     * Where to redirect users after registration.     *     * @var string     */    protected $redirectTo = '/home';    /**     * Create a new controller instance.     *     * @return void     */    public function __construct()    {        $this->middleware('guest');    }    /**     * Get a validator for an incoming registration request.     *     * @param  array  $data     * @return \Illuminate\Contracts\Validation\Validator     */    protected function validator(array $data)    {        return Validator::make($data, [            'name' => ['required', 'string', 'max:255'],            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],            'password' => ['required', 'string', 'min:8', 'confirmed'],        ]);    }    /**     * Create a new user instance after a valid registration.     *     * @param  array  $data     * @return \App\User     */    protected function create(array $data)    {        return User::create([            'name' => $data['name'],            'email' => $data['email'],            'password' => Hash::make($data['password']),        ]);    }}

ResetPasswordController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;use Illuminate\Foundation\Auth\ResetsPasswords;class ResetPasswordController extends Controller{    /*    |--------------------------------------------------------------------------    | Password Reset Controller    |--------------------------------------------------------------------------    |    | This controller is responsible for handling password reset requests    | and uses a simple trait to include this behavior. You're free to    | explore this trait and override any methods you wish to tweak.    |    */    use ResetsPasswords;    /**     * Where to redirect users after resetting their password.     *     * @var string     */    protected $redirectTo = '/home';}

If you run ./vendor/bin/phpunit your PHPUnit tests should pass and you have finished this part of the course. If your test fails, it will display an error which will help you fix the test. Be sure not to change the test, but update the code the test is relying on to get the test to green.

Adding Laravel Dusk for browser tests

We will be creating this app using TDD which involves creating the tests first to describe our application and having them fail at first. We will then write the application code that will get tests passing. In order to do this you should install Dusk as it will be a part of your testing suite via composer require --dev laravel/dusk.

If you’re not using Homestead and don’t have the Chrome driver set up, you might be able to run php artisan dusk:chrome-driver and chmod -R 0755 vendor/laravel/dusk/bin/ to ensure permissions are set correctly.

Once composer has installed the dependancy, you can go ahead a run php artisan dusk:install which will create DuskTestCase.php and ExampleTest.php plus a bunch of other boilerplate code in our tests directory.

If you’re not using Homestead, you may need to change your DuskTestCase.php inside your tests directory. If you’re like me and you’re running Laravel on DDEV, then go ahead and update it to the following…

        return RemoteWebDriver::create(            'http://selenium-hub:4444/wd/hub', DesiredCapabilities::chrome()->setCapability(            ChromeOptions::CAPABILITY, $options        )        );

You may also find this blog post of interest.

To ensure everything runs correctly, you will need to run php artisan dusk which will run our ExampleTest.php. It may take a moment to run, but with any luck you should see a green passing test.

If you receive any errors, then please visit the prerequisites to ensure everything has been installed correctly. Go ahead and delete ExampleTest.php as it will no longer be needed.

Frontend Authentication

Creating your first Laravel Dusk tests

With Dusk setup and ready to go, you can create your first test by running php artisan dusk:make UserLoginTest.php.

Closing thoughts

This has mostly been boilerplate Laravel authentication as we are trying to replicate a traditional Laravel app. In the next episode, you’ll be looking at implementing the frontend authentication and registration using Laravel Dusk.