9 Quick Tips for Auth in Laravel
Published on by PovilasKorop
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:
Auth::routes([ 'login' => true, 'logout' => true, 'register' => true, 'reset' => true, // for resetting passwords 'confirm' => false, // for additional password confirmations 'verify' => false, // for email verification]);
Those parameters just enable or disable some routes.
To understand how they work, you can look at the file AuthRouteMethods in Laravel UI:
return function ($options = []) { // Login Routes... if ($options['login'] ?? true) { $this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); $this->post('login', 'Auth\LoginController@login'); } // Logout Routes... if ($options['logout'] ?? true) { $this->post('logout', 'Auth\LoginController@logout')->name('logout'); } // Registration Routes... if ($options['register'] ?? true) { $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); $this->post('register', 'Auth\RegisterController@register'); } // Password Reset Routes... if ($options['reset'] ?? true) { $this->resetPassword(); } // Password Confirmation Routes... if ($options['confirm'] ?? class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) { $this->confirmPassword(); } // Email Verification Routes... if ($options['verify'] ?? false) { $this->emailVerification(); }};
Tip 2. Laravel UI: Generate Controllers Only
The official documentation specifies this main way of using Laravel UI:
php 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:
php 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.
All you need to do is to add a Middleware called password.confirm
to the route(s) that you want to protect.
Route::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 theauth
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:
Auth::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
:
protected function authenticated(Request $request, $user){ \Auth::logoutOtherDevices(request('password'));}
Also, don’t forget to activate one middleware AuthenticateSession
in app/Http/Kernel.php
file, which is commented out by default:
protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ],
Redirect after Login/Register: Custom Logic
By default, both Laravel LoginController and RegisterController have the same property:
class RegisterController extends Controller{ 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
:
class RouteServiceProvider extends ServiceProvider{ 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:
class RegisterController extends Controller{ protected $redirectTo = RouteServiceProvider::HOME; protected function redirectTo() { if (auth()->user()->role_id == 1) { return '/admin'; } return '/home'; }
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:
php 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:
\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
:
$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ];});
These are default values for the “fake” user that we may create. To do that, we will generate a Seeder file:
php artisan make:seeder UsersSeeder
Then, we open that generated file database/seeds/UsersSeeder.php
and fill run()
method with this:
public function run(){ // This will create 100 users factory(App\User::class, 100)->create();}
To launch that, we need to run this command:
php 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:
trait AuthenticatesUsers{ // ... other methods public function username() { return 'email'; }
You can copy that into your LoginController.php
and just change the value:
class LoginController extends Controller{ use AuthenticatesUsers; // ... other methods public function username() { return 'username'; }}
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.
class LoginController extends Controller{ // ... public function username() { return filter_var(request('email'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username'; }}
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
:
trait ThrottlesLogins{ // ... other methods /** * Get the maximum number of attempts to allow. * * @return int */ public function maxAttempts() { return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5; } /** * Get the number of minutes to throttle for. * * @return int */ public function decayMinutes() { return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1; }}
So, to override those, you may specify properties inside of your LoginController
:
class LoginController extends Controller{ protected $maxAttempts = 3; // Default is 5 protected $decayMinutes = 2; // Default is 1 // ...}
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
:
trait RegistersUsers{ public function register(Request $request) { $this->validator($request->all())->validate(); event(new Registered($user = $this->create($request->all()))); $this->guard()->login($user); if ($response = $this->registered($request, $user)) { return $response; } return $request->wantsJson() ? new Response('', 201) : redirect($this->redirectPath()); }
So your goal is to override it in RegisterController
and return a redirect to your new page, instead of logging in:
class RegisterController extends Controller{ use RegistersUsers; public function register(Request $request) { $this->validator($request->all())->validate(); event(new Registered($user = $this->create($request->all()))); return redirect()->route('your_success_page_route_name'); }
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
:
trait AuthenticatesUsers{ // ... protected function credentials(Request $request) { return $request->only($this->username(), 'password'); }
You just override this in LoginController
and add whatever you want:
class LoginController extends Controller{ // ... protected function credentials(Request $request) { return $request->only($this->username(), 'password') + ['is_active' => 1]; }
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!