DEV Community

Cover image for Laravel custom authentication system
Sujith Kumar
Sujith Kumar

Posted on • Updated on

Laravel custom authentication system

Alt Text
Hey everyone! So, this is my first post and I'm super excited to share some thoughts about this topic and wasting no time, let's get into it 🔥

Synopsis:

  • We will create custom Login and Registration system.
  • We will see some code refactoring, which avoids too much logic inside a single method.

Authentication

Basically, authentication means a lock 🔒 to prevent anyone from accessing protected content unless they are logged in. Laravel provides that in a super elegant way, but we are not limited to it.


Let's discuss some ways to perform Authentication.

1. In built Basic Authentication (we won't use it here)

Note - Laravel below v6 : Laravel has built an auth scaffolding which we can roll out with a single command. Just run php artisan make:auth and it does all the heavy lifting for us, but this has been changed in Laravel v6

Laravel v6 (Latest as of writing this post) : The same command above will not work, rather we have to use two commands, which provides the routes and layouts(views) for authentication endpoints.

composer require laravel/ui

php artisan ui vue --auth
Enter fullscreen mode Exit fullscreen mode

2. Custom Basic Authentication:

Create a fresh laravel app using laravel new application-name command.

Run the following commands,

composer require laravel/ui  // ui scaffolding package

php artisan ui bootstrap  // *optional - adding bootstrap 

npm install && npm run dev
Enter fullscreen mode Exit fullscreen mode

Run php artisan serve to start development server

Steps

[ 1 ] We can use the User schema created by default inside
database/create_users_table.php

[ 2 ] Run php artisan migrate to migrate db

[ 3 ] Create routes for Login and Registration

# File: routes/web.php

// Show Register Page & Login Page
Route::get('/login', 'LoginController@show')->name('login')->middleware('guest');
Route::get('/register', 'RegistrationController@show')
    ->name('register')
    ->middleware('guest');


// Register & Login User
Route::post('/login', 'LoginController@authenticate');
Route::post('/register', 'RegistrationController@register');


// Protected Routes - allows only logged in users
Route::middleware('auth')->group(function () {
    Route::get('/', 'DashboardController@index');

    Route::post('/logout', 'LoginController@logout');
});
Enter fullscreen mode Exit fullscreen mode

Let's break some pieces,

  • You may notice the name at the end, it creates a route named "register". This provides an option of routing to that specific page using the name given., Eg: In our controller we can call redirect()->route("register") which will hit the GET /register endpoint. It also helps to access the route inside blade file {{ route('login') }}
  • We have grouped some of our endpoints with a middleware called "auth". It basically prevents access to those endpoints from unauthenticated users. The middleware "guest" is vice versa of "auth", it allows only unauthenticated users. But if a logged in user visits the route, he will be redirected to the url mentioned inside RedirectIfAuthenticated.php middleware. And we can modify the url too.
  • Middleware can be used on route endpoints or assign it to a group of endpoints. It can also be used inside the controller's constructor method.

[ 4 ] Add this method to User model. It will hash the password before storing it to DB. It is called as Mutator


use Illuminate\Support\Facades\Hash;  // Import Hash facade

public function setPasswordAttribute($password)
{
    $this->attributes['password'] = Hash::make($password);
}
Enter fullscreen mode Exit fullscreen mode

[ 5 ] Run these commands to create controllers for Login, Registration and Dashboard,

php artisan make:controller LoginController
php artisan make:controller RegistrationController
php artisan make:controller DashboardController
Enter fullscreen mode Exit fullscreen mode

[ 6 ] Lets add the logic to display registration page and registering users inside RegistrationController.php

<!-- File: RegistrationController.php -->

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegistrationController extends Controller
{

    public function show()
    {
        return view('register');
    }

    public function register(Request $request)
    {
        $validator = $request->validate([
          'name'      => 'required|min:1',
          'email'     => 'required',
          'password'  => 'required|min:6'
        ]);

        \App\User::create($validator);

        return redirect('/login');
     }

}

Enter fullscreen mode Exit fullscreen mode
  • Show() method returns the register.blade.php file using the global view helper. If register.blade is inside a folder, Eg: "auth/register.blade.php", then we can call it as view('auth.register')
  • Register users:
    • There are number of ways to access the request fields inside a method.
    • Here, we use method injection. It injects the incoming request to the method as an parameter. Request contains some variety of helper methods, here we use validate to check our request fields.
    • Keep in mind that, if the fields (name, email, password) mentioned are not added into $fillable property inside "User" model, then a Mass Assignment Exception will be thrown.

[ 7 ] Adding logic to display login page, logging and logout users inside LoginController.php

<!-- File: LoginController.php -->

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

class LoginController extends Controller
{
    public function show()
    {
        return view('login');
    }

    public function authenticate(Request $request)
    {
        $validator = $request->validate([
            'email'     => 'required',
            'password'  => 'required|min:6'
        ]);

        if (Auth::attempt($validator)) {
            return redirect()->route('dashboard');
        }
    }

    public function logout()
    {
        Session::flush()
        Auth::logout();
        return back();
    }

}

Enter fullscreen mode Exit fullscreen mode
  • show() returns login page
  • authenticate() :
    • Validates request fields.
    • Auth facade is a helper laravel provides. Auth::attempt returns true if authentication was successful or false if it had failed. You can also add an else statement and redirect the user back with an error message.
    • Redirect to dashboard ("/") on successful authentication.
  • logout() : We logout using Auth facade and to make sure the user session is completely removed we flush the session.

[ 8 ] Add the necessary blade files to the views directory,

  • register.blade.php, login.blade.php, dashboard.blade.php

It's time to Refactor

We will make sure our controller method only does exactly what it is intended. This is a simple piece of code to refactor, but the practice will really help out on a large scale.

Code to refactor,

  1. Separate validation logic from both register and authenticate methods to a Request file for each.
  2. Inject the request classes created in step 1 as parameters to authenticate and register methods.
  3. Move the registration logic to a Trait and use it inside controller.

[ 1 ]

  • Separate out the validation logic from register method in RegistrationController.php.
  • Run php artisan make:request RegistrationRequest, this will create a file under App/Http/Requests.

Add the code below to RegistrationRequest.php

// RegistrationRequest.php

public function authorize()
{
    return true;  // Set this to "true" else Unauthorized error will be thrown
}

public function rules()
{
    return [
        'name'      => ['required', 'min:3'],
        'email'     => ['required', 'string', 'email'],
        'password'  => ['required', 'string', 'min:6'],
    ];
}
Enter fullscreen mode Exit fullscreen mode

  • Separate out the validation logic from authenticate method in LoginController.php.
  • Run php artisan make:request LoginRequest, this will create a file under App/Http/Requests.

Add the code below to LoginRequest.php

// LoginRequest.php

public function authorize()
{
    return true;  // Set this to "true" else Unauthorized error will be thrown
}

public function rules()
{
    return [
        'email'     => ['required', 'string', 'email'],
        'password'  => ['required', 'string', 'min:6'],
    ];
}
Enter fullscreen mode Exit fullscreen mode

[ 2 ]

  • We will inject RegistrationRequest into our register method. It will take care of the validation and provides the validated fields to our controller method.
// RegistrationController.php

use App\Http\Requests\RegistrationRequest; // Don't forget to import

public function register(RegistrationRequest $requestFields)  // injected Request class
{

    // This will throw an error, since data passed to create method
    // must be an array, but $requestFileds contains an object.
    // We will fix it in next section
    \App\User::create($requestFields);  

    return redirect('/login');
}
Enter fullscreen mode Exit fullscreen mode
  • Similarly, inject LoginRequest into our authenticate method, it will do the validation and provide the validated fields to our controller method.
// LoginController.php

use App\Http\Requests\LoginRequest;  // Don't forget to import

public function authenticate(LoginRequest $requestFields)
{
    // Returned validated fields also contain the csrf token,
    // therefore, we pick only email and password.
    $attributes = $requestFields->only(['email', 'password']);

    if (Auth::attempt($attributes)) {
        return redirect()->route('dashboard');
    }
}
Enter fullscreen mode Exit fullscreen mode

[ 3 ]

We will create a Trait called RegisterUser

  • Create a folder called Traits inside app folder and create a file RegisterUser.php and the namespace will be App\Traits
<!-- RegisterUser.php -->

<?php

namespace App\Traits;

trait RegisterUser
{
    public function registerUser($fields)
    {
        $user = \App\User::create([
            'name'      => $fields->name,
            'email'     => $fields->email,
            'password'  => $fields->password
        ]);
        return $user;
    }
}

Enter fullscreen mode Exit fullscreen mode

So, we moved our logic from register method to this trait, next we have to use it in our controller or any place we need.

Now, let's use the trait

  • In RegistrationController.php add the below code,
use App\Traits\RegisterUser;  // Don't forget to import

class RegistrationController extends Controller
{
    use RegisterUser;  // call RegisterUser class here
Enter fullscreen mode Exit fullscreen mode

That's how we use a trait, cool right!😎

  • Now change the register method as mentioned below,
public function register(RegistrationRequest $requestFields)
{
    $user = $this->registerUser($requestFields);
    return redirect('/login');
}
Enter fullscreen mode Exit fullscreen mode

Note : $this->registerUser($requestFields) will call the registerUser method inside RegisterUser trait.

Why we separated only the register logic ?

Consider an application where we defined a Super-Admin who can create users inside that application. On that case we need not duplicate the registration process, rather we will just use this trait.

Let's see our final code, the code below only shows the authenticate and register methods, since those were the only places we tweaked.

<!-- LoginController.php -->

<?php

namespace App\Http\Controllers;

use App\Http\Requests\LoginRequest;

class LoginController extends Controller
{
    public function authenticate(LoginRequest $requestFields)
    {
        $attributes = $requestFields->only(['email', 'password']);
        if (Auth::attempt($attributes)) {
            return redirect()->route('dashboard');
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
<!-- RegistrationController.php -->

<?php

namespace App\Http\Controllers;

use App\Traits\RegisterUser;
use App\Http\Requests\RegistrationRequest;

class RegistrationController extends Controller
{
    use RegisterUser;

    public function register(RegistrationRequest $requestFields)
    {
        $user = $this->registerUser($requestFields);
        return redirect('/login');
    }
}

Enter fullscreen mode Exit fullscreen mode

And that's it, let's wrap it up!

  • We created login and registration endpoints and controllers.
  • We moved our validation logic to separate request files.
  • We created a trait and moved registration logic to avoid code duplication.
  • Our controller looks simple and readable.

You can add more customization by adding roles, policies etc., which I leave it for another post.

Github repo link for this project. It includes the blade files too.

Any questions.? Leave them in comments 🤠

See ya!

Top comments (1)

Collapse
 
thisisyusuf profile image
Yusuf

Nice