Creating a Password-Optional Authentication System in Laravel
The trend of getting rid of passwords has picked up in recent months. Medium already does it and Slack gives you the option to forgo entering your password on mobile devices.
Tighten Co. has a great write-up on how to accomplish this in a Laravel application but we wanted to give our users the option to use passwords. Passwords are still a very big part of the design language of the internet and we want to make sure our users are aware of (and comfortable with!) the change before we throw a new login system into the mix.
We’re going to diverge at a couple points from Tighten’s walkthrough. First, we need to keep the password field in the User model and ask for it on registration. We’re also not going to override the login method. We will still use this for the normal, passwordfull authentication.
Let’s start by looking at the routes.php file. If you explode Route::auth() and look at the routes explicitly, you’ll see the following:
// Authentication Routes... Route::get('login', 'Auth\AuthController@showLoginForm'); Route::post('login', 'Auth\AuthController@login'); Route::get('logout', 'Auth\AuthController@logout'); // Registration Routes... Route::get('register', 'Auth\AuthController@showRegistrationForm'); Route::post('register', 'Auth\AuthController@register'); // Password Reset Routes... Route::get('password/reset/{token?}', 'Auth\PasswordController@showResetForm'); Route::post('password/email', 'Auth\PasswordController@sendResetLinkEmail'); Route::post('password/reset', 'Auth\PasswordController@reset');
Part of what we’re going to do is make the passwordless login the default which means the default login view is only going to ask for the email address (or username). In that view we’ll include a link to the passwordfull form so we need to create a new view for that login. To do that just create login_password.blade.php right next to login.blade.php. Then in routes.php create another route to show this form.
Route::get('login/password', 'Auth\AuthController@showPasswordLoginForm');
Then in AuthController.php, add the following function.
/** * Show the form for using Password to login. * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function showPasswordLoginForm() { return view('auth.login_password'); }
Both forms will POST to the same route, so go ahead and make the action point to login like the original form. Next, hop back into routes.php and change the function for that route.
- Route::post('login', 'Auth\AuthController@login'); + Route::post('login', 'Auth\AuthController@resolveLogin');
And create the resolveLogin() function in AuthController.
/** * Figures out which login mechanism to use. * * @param Request $request * @return \Illuminate\Http\Response|mixed */ public function resolveLogin(Request $request) { if($request->has('password')) { return $this->login($request); } else { return $this->loginWithMagicLink($request); } }
Pretty simple, right?
This prompts the user to use the “magic link” style of login first but also gives them the chance to enter their password as a fallback. This can be helpful if they’re on a machine without access to their email and since we didn’t modify the password reset mechanism, that still works too! It’s the best of both worlds, really.
Originally published at metricloop.com on June 28, 2016.