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 to to more complex Passport. Additionally, there are handy starter kits such as Breeze and Jetstream, 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:
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:
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:
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.
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:
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:
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
:
Auth::user()
// or
auth()->user()
// or
$request->user()
Here’s how you can do it:
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:
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:
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.
Now, let’s return to our registry controller and bring everything together:
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:
php artisan make:controller Auth/LoginController
Then let’s add routes :
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:
And here is our LoginRequest
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:
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:
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:
return back()
->withInput($request->only('email'))
->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
And this is the result:
Well, our next task is to finalize the authentication process and focus on implementing the logout feature. To begin, we’ll create a controller:
php artisan make:controller Auth/LogoutController
And ensure its routes are properly defined:
Route::post('logout', LogoutController::class)->name('logout');
Since we have only one function in controller, I suggest utilizing an invocable method:
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:
<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:
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:
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:
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:
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.
Happy coding!
Top comments (0)