9 Quick Tips for Auth in Laravel

News

August 25th, 2020

authtips.jpg

Laravel has a great out-of-the-box Auth system, but surely we need to customize things here and there. For some of them, no need to look for external packages or write a lot of custom code, let’s explore what interesting abilities are hiding under the hood of Auth.

Tip 1. Auth::routes() Parameters

We all probably know the method Auth::routes() that comes from the Laravel UI package (before Laravel 7, it was included in the core).

But did you know it may accept an array of parameters to enable/disable certain Auth routes?

As of Laravel 7, here are possible parameters, with their default values:

1Auth::routes([
2 'login' => true,
3 'logout' => true,
4 'register' => true,
5 'reset' => true, // for resetting passwords
6 'confirm' => false, // for additional password confirmations
7 'verify' => false, // for email verification
8]);

Those parameters just enable or disable some routes.

To understand how they work, you can look at the file AuthRouteMethods in Laravel UI:

1return function ($options = []) {
2 // Login Routes...
3 if ($options['login'] ?? true) {
4 $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
5 $this->post('login', 'Auth\LoginController@login');
6 }
7
8 // Logout Routes...
9 if ($options['logout'] ?? true) {
10 $this->post('logout', 'Auth\LoginController@logout')->name('logout');
11 }
12
13 // Registration Routes...
14 if ($options['register'] ?? true) {
15 $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
16 $this->post('register', 'Auth\RegisterController@register');
17 }
18
19 // Password Reset Routes...
20 if ($options['reset'] ?? true) {
21 $this->resetPassword();
22 }
23
24 // Password Confirmation Routes...
25 if ($options['confirm'] ??
26 class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) {
27 $this->confirmPassword();
28 }
29
30 // Email Verification Routes...
31 if ($options['verify'] ?? false) {
32 $this->emailVerification();
33 }
34};

Tip 2. Laravel UI: Generate Controllers Only

The official documentation specifies this main way of using Laravel UI:

1php artisan ui vue --auth

But what if you don’t need the visual UI? What if you’re creating only an API-based project, and you don’t have any front-end on the Laravel side?

You can still use Laravel Auth and its Controllers. Install Laravel UI package and run this:

1php artisan ui:controllers

It will generate only app/Http/Controllers/Auth contents, so you don’t need Blade/Vue files to use them.

See the implementation of this Artisan command in Github repository.


Tip 3. Re-Confirm Password for Important Settings

Have you ever maintained a Github repository, and tried to change its access settings? Then Github asks you to re-enter your password again, just to make sure it’s you.

Since Laravel 6.2, we also have that feature in the framework.

Laravel Password Confirm

All you need to do is to add a Middleware called password.confirm to the route(s) that you want to protect.

1Route::get('/secrets', 'SecretsController@show')->middleware('password.confirm');

Quoting Dries Vints from the official feature release article:

If you attempt to access the route, you will be prompted to confirm your password, similar to what you may have seen on other applications like GitHub.

Confirming the password will store a timestamp in the user’s session that lasts for three hours by default so users do not have to enter their password during that period again.

You may customize this duration using a new password_timeout configuration option in the auth configuration file._


Tip 4. Logout Other Devices

From Laravel 5.6, we have a separate method to automatically log out any other devices or browsers that are logged in with our account:

1Auth::logoutOtherDevices($password);

Typical usage of this would be to log out other devices when the current device is successfully logged in. To do that, we override a method authenticated() from a Trait AuthenticatesUsers.php, and put this into app/Http/Controllers/Auth/LoginController.php:

1protected function authenticated(Request $request, $user)
2{
3 \Auth::logoutOtherDevices(request('password'));
4}

Also, don’t forget to activate one middleware AuthenticateSession in app/Http/Kernel.php file, which is commented out by default:

1protected $middlewareGroups = [
2 'web' => [
3 \App\Http\Middleware\EncryptCookies::class,
4 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
5 \Illuminate\Session\Middleware\StartSession::class,
6 // \Illuminate\Session\Middleware\AuthenticateSession::class,
7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,
8 \App\Http\Middleware\VerifyCsrfToken::class,
9 \Illuminate\Routing\Middleware\SubstituteBindings::class,
10 ],

Redirect after Login/Register: Custom Logic

By default, both Laravel LoginController and RegisterController have the same property:

1class RegisterController extends Controller
2{
3 protected $redirectTo = RouteServiceProvider::HOME;

So you can specify what URL to redirect to after successful login/registration. The default value is in app/Providers/RouteServiceProvider.php:

1class RouteServiceProvider extends ServiceProvider
2{
3 public const HOME = '/home';

How can you customize it?

First, you can change the value of that $redirectTo property, to some other constant, and maybe separately for Login and Registration.

But what if you have a more complex logic of dynamic redirect, that depends on a user role, for example?

You can create a method in those Auth Controllers, call it redirectTo(), and specify your conditions inside. That method will override any values of $redirectTo property.

See example:

1class RegisterController extends Controller
2{
3 protected $redirectTo = RouteServiceProvider::HOME;
4
5 protected function redirectTo()
6 {
7 if (auth()->user()->role_id == 1) {
8 return '/admin';
9 }
10 return '/home';
11 }

Tip 5. Quickly Create New Users

What if you need to create one new user, and you don’t have a registration form ready?

Just open Laravel Tinker in your Terminal:

1php artisan tinker

If you’re not familiar with Tinker, it’s a command-line tool to execute any Laravel/PHP code. So, inside of that, you can easily create a user, typing this Eloquent command and hitting Enter:

1\App\User::create(['name' => 'Admin', 'email' => 'admin@admin.com', 'password' => bcrypt('somesecurepassword')]);

But, what if you need to create many users for testing, Like, 10, or 100, or 1000? No problem, we can use a Factory class that comes by default with Laravel, in database/factories/UserFactory.php:

1$factory->define(User::class, function (Faker $faker) {
2 return [
3 'name' => $faker->name,
4 'email' => $faker->unique()->safeEmail,
5 'email_verified_at' => now(),
6 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
7 'remember_token' => Str::random(10),
8 ];
9});

These are default values for the “fake” user that we may create. To do that, we will generate a Seeder file:

1php artisan make:seeder UsersSeeder

Then, we open that generated file database/seeds/UsersSeeder.php and fill run() method with this:

1public function run()
2{
3 // This will create 100 users
4 factory(App\User::class, 100)->create();
5}

To launch that, we need to run this command:

1php artisan db:seed --class=UsersSeeder

You can read more about database seeding in the official Laravel documentation.


Tip 6. Login with Email and/or Username

By default, Laravel users authenticate with email and password. But what if your identifier is not email? Some kind of username, for example.

You can change it easily by overriding one method from the AuthenticatesUsers.php trait.

Here’s the default value:

1trait AuthenticatesUsers
2{
3 // ... other methods
4
5 public function username()
6 {
7 return 'email';
8 }

You can copy that into your LoginController.php and just change the value:

1class LoginController extends Controller
2{
3 use AuthenticatesUsers;
4
5 // ... other methods
6
7 public function username()
8 {
9 return 'username';
10 }
11}

Let’s take one step even further. What if your users can log in with email OR username? So there’s an input field called “Email/username” and they can put in one or another.

Let’s add a “trick” to the same username() method from above. We check if the entered string is an email, otherwise, we treat it as a username. That check is a PHP function, not even Laravel.

1class LoginController extends Controller
2{
3 // ...
4
5 public function username()
6 {
7 return filter_var(request('email'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
8 }
9}

Notice: don’t forget that in your login form, you need to change input type="email" to type="text"


Tip 7. Too Many Login Attempts: Customize Parameters

If you try to log in with invalid credentials more than five times within the same minute, you will get blocked, with a message Too many login attempts. Please try again in X seconds.

That block will be active for 1 minute, and it is unique to the user’s username/e-mail and their IP address.

You can customize those parameters:

  • Amount of invalid attempts within a minute (default five attempts)
  • How many minutes to block logins (default 1 minute)

Those two parameters are inside of a Trait ThrottlesLogins:

1trait ThrottlesLogins
2{
3 // ... other methods
4
5 /**
6 * Get the maximum number of attempts to allow.
7 *
8 * @return int
9 */
10 public function maxAttempts()
11 {
12 return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
13 }
14
15 /**
16 * Get the number of minutes to throttle for.
17 *
18 * @return int
19 */
20 public function decayMinutes()
21 {
22 return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
23 }
24}

So, to override those, you may specify properties inside of your LoginController:

1class LoginController extends Controller
2{
3 protected $maxAttempts = 3; // Default is 5
4 protected $decayMinutes = 2; // Default is 1
5
6 // ...
7}

Tip 8. Registration: Disable Auto-Login

By default, a newly registered user is automatically logged in and redirected to the home page.

If you want to disable that and show some “success” page instead, without automatically creating a user’s session, here’s what you can do.

The original registration method is inside the Trait RegistersUsers:

1trait RegistersUsers
2{
3 public function register(Request $request)
4 {
5 $this->validator($request->all())->validate();
6
7 event(new Registered($user = $this->create($request->all())));
8
9 $this->guard()->login($user);
10
11 if ($response = $this->registered($request, $user)) {
12 return $response;
13 }
14
15 return $request->wantsJson()
16 ? new Response('', 201)
17 : redirect($this->redirectPath());
18 }

So your goal is to override it in RegisterController and return a redirect to your new page, instead of logging in:

1class RegisterController extends Controller
2{
3 use RegistersUsers;
4
5 public function register(Request $request)
6 {
7 $this->validator($request->all())->validate();
8
9 event(new Registered($user = $this->create($request->all())));
10
11 return redirect()->route('your_success_page_route_name');
12 }

Tip 9. Login: Additional Check with Email/Password

What if you need some extra check, in addition to the default email and password? For example, you want to check if the user is active, or not banned.

You can add extra elements to credentials array, which is defined in the trait AuthenticatesUsers:

1trait AuthenticatesUsers
2{
3 // ...
4
5 protected function credentials(Request $request)
6 {
7 return $request->only($this->username(), 'password');
8 }

You just override this in LoginController and add whatever you want:

1class LoginController extends Controller
2{
3 // ...
4
5 protected function credentials(Request $request)
6 {
7 return $request->only($this->username(), 'password') + ['is_active' => 1];
8 }

Notice: this is an interesting quick tip, but I would advise you to perform such extra check in a separate Middleware, then you could provide a more explicit error message to the user, instead of a default credentials error.


That’s it, these are the quick tips, but there’s much more to be extended with custom code and external packages. So, stay tuned for more articles to come on that topic!

Filed in:

PovilasKorop

A web-developer with 15+ years experience, founder of Laravel QuickAdminPanel generator.

Sharing Laravel lessons on Youtube with channel Laravel Business.