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.
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.
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 nohome
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 nohome
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.