Laravel Authentication Guide

Howdy!

In addition to the usual Email/Password authentication, Laravel provides more than that to authenticate users.

In this guide, we will discover the usual one method of authentication, and we will discover other methods.

Author
Authentication

Installing & Configuring Fortify

According to the documentation, Fortify is frontend agnostic authentication backend implementation for Laravel. That means Fortify will only handle the backend login for us. All we have to do is building the UI, and that's what I like about Fortify, the freedom to build the UI as you want.
After knowing what Fortify is, now let's dive in.
To install Fortify, let's run this command in the terminal of your project :

composer require laravel/fortify

After the installation, let's publish the config file:

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

And then we will migrate the changes of Fortify

php artisan migrate

Next thing to do is registering the Fortify service provider, into the application, so do it, open config/app.php and in the providers array, add FortifyServiceProvider , as follows :

    'providers' => ServiceProvider::defaultProviders()->merge([
...
        App\Providers\FortifyServiceProvider::class,
    ])->toArray(),

After registering the service provider, the next thing to do is to enable the features. Those features will be found on config/fortify.php file.
For the moment we will enable registration, email verification and reset passwords. So after all, here is what the file will look like.

    'features' => [

        Features::registration(),

        Features::resetPasswords(),

        Features::emailVerification(),

...

    ],

Email/Password Authentication

And we will begin with the registration.

Registration

The first thing to do is to create and register the views, so let's dive in.
Let's create a file called resources/views/auth/register.blade.php

Note
You can name/place your files whatever/whenever you want. It's up to you.

After creating the file, we will put a basic HTML form, with basic styling with Tailwind.

<form action="{{ route('register') }}" method="POST">
	@csrf
	<div class="grid gap-y-4">
		<div>
			<label for="name" class="mb-2 block text-sm dark:text-white">Name</label>
			<div class="relative">
				<input type="text" id="name" name="name" value="{{ old('name') }}" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="name-error">
				@error('name')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('name')
				<p class="mt-2 text-xs text-red-600" id="name-error">{{ $message }}</p>
			@enderror
		</div>

		<div>
			<label for="email" class="mb-2 block text-sm dark:text-white">{{ __('Email') }}</label>
			<div class="relative">
				<input type="email" id="email" name="email" value="{{ old('email') }}" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="email-error">
				@error('email')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('email')
				<p class="mt-2 text-xs text-red-600" id="email-error">{{ $message }}</p>
			@enderror
		</div>
		<div>
			<label for="password" class="mb-2 block text-sm dark:text-white">{{ __('Password') }}</label>
			<div class="relative">
				<input type="password" id="password" name="password" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="password-error">
				@error('password')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('password')
				<p class="mt-2 text-xs text-red-600" id="password-error">{{ $message }}</p>
			@enderror
		</div>
		<div>
			<label for="password_confirmation" class="mb-2 block text-sm dark:text-white">{{ __('Confirm Password') }}</label>
			<div class="relative">
				<input type="password" id="password_confirmation" name="password_confirmation" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="password_confirmation-error">
				@error('password_confirmation')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('password_confirmation')
				<p class="mt-2 text-xs text-red-600" id="password_confirmation-error">{{ $message }}</p>
			@enderror
		</div>
		<div class="flex flex-col">
			<div class="flex items-center">
				<div class="flex">
					<input id="tos" name="tos" type="checkbox" class="text-primary-600 focus:ring-primary-500 dark:checked:border-primary-500 dark:checked:bg-primary-500 mt-0.5 shrink-0 cursor-pointer rounded border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:focus:ring-offset-gray-800">
				</div>
				<div class="ml-3">
					<label for="tos" class="text-sm dark:text-white">{{ __('I accept the') }} <a class="text-primary-600 font-medium decoration-2 hover:underline" href="#">{{ __('Terms and Conditions') }}</a></label>
				</div>
			</div>
			@error('tos')
				<p class="mt-2 text-xs text-red-600" id="password_confirmation-error">{{ $message }}</p>
			@enderror

		</div>
		<div class="mx-auto">
			<x-turnstile data-action="register" />
		</div>
		<button type="submit" class="bg-primary-500 hover:bg-primary-600 focus:ring-primary-500 inline-flex items-center justify-center gap-2 rounded-md border border-transparent py-3 px-4 text-sm font-semibold text-white transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-800">{{ __('Sign up') }}</button>
	</div>
</form>

So what we will have here, is a basic HTML Form with action to the route register (Fortify already registered the route).
We have four inputs, name , email , password and password_confirmation .
And foreach input, we have the error bags, which we can access using @error directive, and show the message using $message variable.

After creating our view, we will register it in FortifyServiceProvider.php. After opening the file,
in the boot function, we will register the view.

public function boot(): void
{
...
	Fortify::registerView(fn () => view('auth.register'));
...
}

And then opening the application and visiting the /register route, we're supposed to have the view we created.

Register view

Next thing we will try to register a user. After filling up the user information, you will be supposed to get to the /home route.

Note
If you have a new installation, you will get a 404 not found. Because there is no home route.

Now, after finishing the register flow, now it will come the login flow.
The same will be applied here, we will setup the form, and Fortify will handle the rest for us, so let's dive in.
First thing to do is to create the file resources/auth/login.blade.php , which is simpler. We will have 2 inputs email and password.
As usual, we will have the basic styling with Tailwind.

<form action="{{ route('login') }}" method="POST">
	@csrf
	<div class="grid gap-y-4">
		<div>
			<label for="email" class="mb-2 block text-sm dark:text-white">{{ __('Email') }}</label>
			<div class="relative">
				<input type="email" id="email" name="email" value="{{ old('email') }}" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="email-error">
				@error('email')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('email')
				<p class="mt-2 text-xs text-red-600" id="email-error">{{ $message }}</p>
			@enderror
		</div>

		<div>
			<div class="flex items-center justify-between">
				<label for="password" class="mb-2 block text-sm dark:text-white">{{ __('Password') }}</label>
				<a class="text-primary-600 text-sm font-medium decoration-2 hover:underline" href="{{ route('password.request') }}">{{ __('Forgot password?') }}</a>
			</div>
			<div class="relative">
				<input type="password" id="password" name="password" class="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md border-gray-200 py-3 px-4 text-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" required aria-describedby="password-error">
				@error('password')
					<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
						<svg class="h-5 w-5 text-red-500" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
							<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
						</svg>
					</div>
				@enderror
			</div>
			@error('password')
				<p class="mt-2 text-xs text-red-600" id="password-error">{{ $message }}</p>
			@enderror
		</div>

		<div class="flex items-center">
			<div class="flex">
				<input id="remember-me" name="remember" type="checkbox" class="text-primary-600 focus:ring-primary-500 dark:checked:border-primary-500 dark:checked:bg-primary-500 mt-0.5 shrink-0 cursor-pointer rounded border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:focus:ring-offset-gray-800">
			</div>
			<div class="ml-3">
				<label for="remember-me" class="cursor-pointer text-sm dark:text-white">{{ __('Remember me') }}</label>
			</div>
		</div>
		<button type="submit" class="bg-primary-500 hover:bg-primary-600 focus:ring-primary-500 inline-flex items-center justify-center gap-2 rounded-md border border-transparent py-3 px-4 text-sm font-semibold text-white transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-800">{{ __('Sign in') }}</button>
	</div>
</form>

After creating the file, we will edit the FortifyServiceProvider to add the login view.

public function boot(): void
{
...
	Fortify::registerView(fn () => view('auth.register'));
	Fortify::loginView(fn () => view('auth.login'));
...
}

After adding the login view, try to visit /login and you will see the rendered HTML, and filling up the user information, you are supposed to be redirected to home route.

Note
If you have a new installation, you will get a 404 not found. Because there is no home route.

Social Authentication

In addition to the usual email/password, Laravel provides another convenient way to authenticate users to your application, which is Social Authentication.
Laravel has a first-party package called Socialite, which brings OAuth to a much less hassle.

So the first thing to do here is to install Socialite

composer require laravel/socialite

After installing the package, we will add the credentials for each provider we will have.
For this guide, we will use X (formerly Twitter).
After creating your new application on X Developers Portal (or if you already have one).
Grab the API key & the API key secret, and create another entry in config/services.php as shown below.

...
    'twitter' => [
        'client_id' => env('X_API_KEY'),
        'client_secret' => env('X_SECRET_KEY'),
        'redirect' => 'https://test.tst/auth/callback',
    ],
...

In the redirect key, replace test.tst with your current application URL.

After adding the credentials, now let's head and define them in our .env file.

X_API_KEY=api_key_here
X_SECRET_KEY=api_secret_here

After defining our credentials, let's create a new controller responsible for the authentication.

php artisan make:controller SocialController

In our newly created controller, we will add two methods, one for the redirect to the OAuth provider, and the other one is for receiving the callback from the provider after authentication.



public function redirect()
{
	return Socialite::driver('twitter')->redirect();
}


public function callback()
{
    $user = Socialite::driver('twitter')->user();	
}

As you can see here, the first method will redirect us to Twitter authentication, and the second one is the callback from Twitter.
After creating those two methods, now let's create the corresponding routes for them.
So in routes/web.php file, add the following

Route::get('/auth/redirect', [SocialController::class, 'redirect'])->name('twitterLogin');

Route::get('/auth/callback', [SocialConteoller::class, 'callback']);

After adding the routes, now let's head to where you want to put the link to login the user. Usually it's where the login form, but you can put it wherever you want to. We will keep it simple here.

<a href="{{ route('twitterLogin') }}">Login with Twitter</a>

After setting up everything, now let's go back to our SocialController.php and edit the callback function.
Usually, when a new user will login using social providers (Twitter in our case), you want to create a new account for them, and authenticate them, otherwise, if they exist, simply authenticate them.
So we will edit the callback function accordingly:

public function callback()
{
    $twitterUser = Socialite::driver('twitter')->user();
    $user = User::firstOrCreate(
        [
            'provider_id' =>  $twitterUser->getId()
        ],
        [
            'name' => $twitterUser->getName(),
            'email' => $twitterUser->getEmail()
        ]
    );
    Auth::login($user);
    return redirect('/home');
}

Here we are using Eloquent method firstOrCreate which will search the model with the specified provider_id , if it's found, we will authenticate the user, otherwise, it will create a new user with the information provided and then authenticate.
But there is somethings to take into consideration here.
First thing is, we have to add provider_id to our users table.

  • For our case, sometimes there will be no email, so you may want to make the email as well nullable in the users table.
  • When the user will use only the social login, also you may want to mark the password field nullable as well.
  • If you are planning to use more than one social provider, you may consider adding the email as a parameter for the search as well, because many users may use the same email across various platforms, for example, Twitter and Google. You don't want to create two different users, while they are the same user after all.
    All of those are just notes to consider, after all, the context of your application matters, and you know what will work for you.

Those are the most used Laravel Authentication methods, and the easiest ones.
If you have any comments / some fixes, please let me know.
Thank you.