Awwwards
Migrating Multi Auth to Laravel 5.3's Hero Image

Migrating Multi Auth to Laravel 5.3

A couple months ago I wrote a piece on setting up multi auth in Laravel 5.2 trying to uncover the intricacies of setting up multiple authentication the “Laravel way”.

Laravel 5.3 was released about a week ago and introduced some significant changes to the authentication mechanism. It splits up the AuthController into a LoginController and a RegisterController, which actually makes a lot of sense. There’s also a similar split in the former PasswordController that focuses on the “forgot password” interaction vs the “reset password” interaction. Really sensible changes here.

So let’s see what it takes to migrate a current 5.2 app with multi auth to an upgraded 5.3 installation.

First, I highly recommend using Laravel Shift to start the upgrade process. It’ll automate about 80% of changes you need to make and point you in the right direction for the remaining 20%. It’s incredibly valuable at less than $10!

Also, this will assume that you already have a working multi auth in Laravel 5.2 set up and namespaced like it is in my previous walkthrough.

Login and Register

Let’s fix up the User class. We need two new controllers in the normal Auth namespace: LoginController and RegisterController.

The LoginController uses a different trait, AuthenticatesUsers, which as you can surmise, does not include the “register” process. It does however still takes advantage of defining a $redirectTo path, which I’ve set as /admin. I used to override handleUserWasAuthenticated() so I could dispatch a custom logger Job, which was a terrible idea in hindsight. That function is now called sendLoginResponse() which calls the authenticated() function and redirects to the intended destination. My logger dispatch is now in authenticated() instead of the “handler” or “sender” functions that actually evaluate the login.

The nice thing about this new controller is now it doesn’t include the validator() or create() functions because it only focuses on authenticating users!.

The RegisterController also has the more specific trait RegistersUsers and the same $redirectTo path variable. The validator() andcreate()functions are defined in here. I’m not allowingUser` registration so this class is largely ignored.

Before we get into passwords, let’s look at the Auth\Customer namespace. The changes here are going to be the same mentioned above with some additions. Previously we defined member variables to set the various views needed but this time we define functions. We also need to define the guard() function.

    public function showPasswordLoginForm()
    {
        return view('auth.customers.login_password');
    }

    public function showLoginForm()
    {
        return view('auth.customers.login');
    }

    protected function guard()
    {
        return Auth::guard('customer');
    }

If you haven’t already done so, take a look at creating a password-optional authentication system for your app.

Again, the RegisterController is going to have similar changes but we need to define new functions instead of member variables.

    public function showRegistrationForm()
    {
        return view('auth.customers.register');
    }

    protected function guard()
    {
        return Auth::guard('customer');
    }

And Now Passwords

User Passwords

Back in User land, we’re splitting the PasswordController into ForgotPasswordController and ResetPasswordController. In “Forgot”, we only need the SendsPasswordResetEmails trait and the constructor. In “Reset”, we need the ResetsPasswords trait, the constructor, and maybe a $redirectTo if you’re customizing that.

With the introduction of Notifications in 5.3, the password reset email is handled through the notifications system. Here, you might want to customize the path that’s sent to your Users when they request to reset their password. To do that, create a new Notification.

php artisan make:notification UserResetPassword

Take a peek at vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php to see how the default is implemented. The only thing I changed was the action() url.

Spoiler! You’re going to need to make a new notification for Customers, CustomerResetPassword.

Customer Passwords

We’re going to make the same logical split for Customers but, of course, we need to add a few things. Remember, we’re replacing member variables with functions.

    // Customer/ForgotPasswordController.php

    public function showLinkRequestForm()
    {
        return view('auth.customers.passwords.email');
    }

    public function broker()
    {
        return Password::broker('customers');
    }

    // Customer/ResetPasswordController.php

    public function showResetForm(Request $request, $token = null)
    {
        return view('auth.customers.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }

    public function broker()
    {
        return Password::broker('customers');
    }

    protected function guard()
    {
        return Auth::guard('customer');
    }

About those new ResetPassword notifications; you actually need to override a function in your User and Customer classes to use the new notification. Simply done:

    // App/User.php

    public function sendPasswordResetNotification($token)
    {
        $this->notify(new UserResetPassword($token));
    }

    // App/Customer.php
		
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new CustomerResetPassword($token));
    }

Routing

Can’t forget to update the routes file! If you’ve customized your authentication routes, hop into your routes file and swap out the AuthController for LoginController/RegisterController and PasswordController for ForgotPasswordController/ResetPasswordController where it makes sense. If you’re already using Route::auth() then that’s all taken care of for you!

Middleware

5.3 moves the Authenticate middleware into the core and introduces the AuthenticationException. This means that whatever rules you might have in the auth middleware are now ignored. For me, I was checking if Users had a temporary password so I needed to create a new middleware just for that purpose.

I was also redirecting to different pages based on which guard was used during authentication, i.e. Customers and Users need to go to different pages when they fail to log in.

If you look at App\Exceptions\Handler.php you’ll see a new function called unauthenticated() that handles AuthenticationException. The problem here is that you can’t tell which guards were present in the $request or the exception itself. I’ve opened an issue about this but it got closed after a workaround was proposed. I’m using the workaround, but it’s not my preferred option.

if($request->is(‘admin*’) {
    // redirect to admin login page
}
return redirect()->guest(‘login’);

I hope that in the future the exception would provide contextual information about the attempt, not just that it failed.

Cleaning Up

Finally, you can trash AuthController and PasswordController now because they’ve been fully subsumed by the new controllers. You can also delete the Authenticate middleware in your app folder.

In all, I think this was a fantastic release for polishing off authentication in Laravel. Splitting up the auth and password controllers makes a ton of logical sense, and getting multi auth up and running wasn’t too much work either!


Patrick Guevara's Profile Picture

Patrick Guevara


Chief Software Engineer

Patrick cofounded Metric Loop on the dream of building really great software with a clean, transparent approach. He lives in Austin, Texas with his wife Jess and their dog named Moose.