loading...

Laravel: How to let user login with Email or Username

pramanadiputra profile image Ida Bagus Gede Pramana Adi Putra Updated on ・5 min read

Laravel by default only let users to login with their Email Address. Now, what if you want to let your users to login with email or even username..?

To achieve this, you only have to change some codes in the Laravel default authentication codes.

Let's start. And I assume you've already set up your Laravel project and migrating Laravel default auth. If you haven't just write command php artisan make:auth and then php artisan migrate.

The commands above make Laravel provides basic and default authentication for you, so you don't have to make Login, Register, Forgot Password & Reset Password manually.

Step by step

Open loginController.php
By default, you will only see these codes inside your login controller

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

This controller only has attributes and one constructor, so how does it handle the login function? It's where the OOP plays its part.

Notice there's use AuthenticatesUsers; statement, and this is where Laravel built its default login functionality. So now, let's check the AuthenticateUsers class.

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait AuthenticatesUsers
{
    use RedirectsUsers, ThrottlesLogins;

    /**
     * Show the application's login form.
     *
     * @return \Illuminate\Http\Response
     */
    public function showLoginForm()
    {
        return view('auth.login');
    }

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function validateLogin(Request $request)
    {
        $request->validate([
            $this->username() => 'required|string',
            'password' => 'required|string',
        ]);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        return $request->only($this->username(), 'password');
    }

    /**
     * Send the response after the user was authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user())
                ?: redirect()->intended($this->redirectPath());
    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        //
    }

    /**
     * Get the failed login response instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function sendFailedLoginResponse(Request $request)
    {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ]);
    }

    /**
     * Get the login username to be used by the controller.
     *
     * @return string
     */
    public function username()
    {
        return 'email';
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        return $this->loggedOut($request) ?: redirect('/');
    }

    /**
     * The user has logged out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function loggedOut(Request $request)
    {
        //
    }

    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return Auth::guard();
    }
}

If you are a beginner and just new to the OOP world, you might be feeling overwhelming when you look at these lines of code. But don't. Now we've taken a look inside the class that handles login functionality, and now your question would likely be "how do we customize it to fulfil our needs?"

Well, you don't! Remember that the location of' AuthenticateUsers' class is at Illuminate\Foundation\Auth\AuthenticatesUsers under Vendor directory. Don't edit or change whatever lies in this class, or your Composer will upset!.

Laravel is an Object-Oriented framework. So we are applying the overriding OOP method.

We want our app to let user login with either email or username, so let's take a look at which method that handles this part inside AuthenticateUsers. And you may have already noticed that this method username() describe what it does.

/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
    return 'email';
}

That comment above tells us that it returns email as the login username that used by the controller. Now, this is the method that we have to override.

Copy and paste that username() method to your loginController.php. Now instead of returning email, we need a way to return either email or username.

Now here's the part where we have to check our login form. In our default login form, look for the default email input and change the name from name="email" to name="identity". Later, it sends identity as an HTTP request to our Laravel.

Back to username() method again, we need to implement a way to check whether the HTTP request email that comes in as an email address or username. We can do this by checking if there's an "@" within the data that sent. Luckily PHP has built a function to do this. If there's an "@", then the HTTP request email is indeed an email address. Otherwise, it's a username.

/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
     $login = request()->input('identity');

     $field = filter_var($login, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
     request()->merge([$field => $login]);

     return $field;
}

What the above code does is to filter the HTTP request that comes in if there's an @ or if it's an email by using FILTER_VALIDATE_EMAIL. If it's an email then return email, otherwise, return username.

After doing this, we only have one to-do left, and it's to overriding the validation request. In your login controller class, add these codes below:

/**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function validateLogin(Request $request)
    {
        $messages = [
            'identity.required' => 'Email or username cannot be empty',
            'email.exists' => 'Email or username already registered',
            'username.exists' => 'Username is already registered',
            'password.required' => 'Password cannot be empty',
        ];

        $request->validate([
            'identity' => 'required|string',
            'password' => 'required|string',
            'email' => 'string|exists:users',
            'username' => 'string|exists:users',
        ], $messages);
    }

The default validation only checks for the email, now we've changed it to also check for both email and username.

Now you have a working solution to let your user login with either an email address or username.

If you have any question, just let me know :)

This post updated on Mei, 5th, 2020. Fixed some grammar mistakes, for a better reading experience for future readers.

Discussion

pic
Editor guide
Collapse
rafikta profile image
rafikta

Thank you, simple and works, except for the error messages not showing like @Juhwan said.
I had to change the identity field to username, now all works with proper error message.

Collapse
pramanadiputra profile image
Ida Bagus Gede Pramana Adi Putra Author

You're welcome. Glad it helps you!

Collapse
vongochaiit profile image
ngoc hai

hello ! how can I customize notifications about it ?

Collapse
saleempbt5 profile image
saleempbt5

I tried your solution, unfortunately none of the error messages are showing, any other possibility ?

Collapse
webpajooh profile image
WebPajooh

Thanks, It's so useful.

Collapse
bossanovajju profile image
Juhwan Kim

Hi~ Your tips really helped me a lot, Thank you!
Login as user or email ok but I have a problem.
No error message is displayed when the entered user information (name,email,password) is incorrect.
I think the code in ‘login.blade.php’ is wrong.
I want to know what's wrong with the code in the link.
please help me~thank you.
gist.github.com/BossanovaJJu/4be79...

Collapse
pramanadiputra profile image
Ida Bagus Gede Pramana Adi Putra Author

Juhwan Kim, have a read on Rafikta's reply!

Collapse
jcbolor profile image
JC

Method Illuminate\Validation\Validator::validateUsername does not exist.
I got this error when login in using username but if email its working. Please help me resolve the problem. thank you.

Collapse
mudanakomang profile image
mudanakomang

keren Bli