5

When building my application, I used the Laravel authentication scaffolding by running php artisan make:auth which was great and saved me a lot of time.

However, I am running into an issue with users not being able to login because they can't remember what case they used for their email when they originally signed up.

For example, a user that signed up with Myemail@domain.com can not login with myemail@domain.com.

I understand from my research that technically the part prior to the @ is case sensitive by virtue of the spec, and that the two emails above could, in theory, belong to two different people. In my situation though, I don't foresee that being enough of a likelihood that I would need to enforce this on login.

I'm using a Postgres database so I'm looking to just replace the email database lookup in Auth::attempt() with ilike instead of like if possible. However, I can't for the life of me find where this is in the source and therefore have no idea how I would go about overwriting this.

Tl;dr how do I override Laravel's default authentication to do email lookup in a case insensitive manner?

Solution

I ended up solving the issue by first taking @btl advice and implementing a mutator method on the email attribute. I didn't want to have to go through and change all the emails for existing users, and felt like in case I ever did run into an issue in which I'd like to have case-sensitivity back, I wanted a solution that could be undone easily.

app/User.php

public function getEmailAttribute($value) {
    return strtolower($value);
}

app/Http/Controllers/Auth/LoginController.php

protected function credentials()
{
    $username = $this->username();
    $credentials = request()->only($username, 'password');
    if (isset($credentials[$username])) {
        $credentials[$username] = strtolower($credentials[$username]);
    }
    return $credentials;
}

Unfortunately I couldn't figure out how to apply this same logic to the ForgotPasswordController.php but for now I'll live. Logging in seems to work regardless of email case now.

Community
  • 1
  • 1
brianjohnhanna
  • 343
  • 4
  • 12
  • I just tried to log into all of my Laravel apps with a randomly cased email and it worked every time. I don't think it should be case sensitive. – Jeff Jan 25 '18 at 20:17
  • @jeff I wouldn't have thought so either, but for some reason in Laravel 5.5 I am having this issue across a couple instances. – brianjohnhanna Jan 25 '18 at 20:46

5 Answers5

10

Add an mutator method on your user model to make the email all lowercase:

public function setEmailAttribute($value)
{
    $this->attributes['email'] = strtolower($value);
}

Whenever a user registers, the email will always be converted to lower case.

Or, use an accessor on the user model:

public function getEmailAttribute($value)
{
    return strtolower($value);
}

That way the original value is preserved in the database, but whenever you call $user->email it will be lowercase. You'd just need to convert your user's input when logging to to lowercase when making the strict comparison.

  • 2
    But what happens if the user now tries to login with uppercase E-Mail address? My guess is that he receives 'invalid credentails' which is very confusing for users. – Simon Fakir Jun 21 '18 at 19:02
  • I agree with Simon. You would have users create accounts or have existing accounts and now they don't know how to login because the case of their email has been changed. The unique check in the validator needs to be case insensitive. – brandonbanks Feb 19 '20 at 02:17
  • Accessor method is great and doesn't effect registrations/logins on my app. The reason is that Eloquent searches seem for me to be case insensitive, while other collection methods like `firstWhere()` are. – Summer Developer Nov 05 '20 at 01:46
3

Also you can make your custom attempt like.

 $credentials = ['email' => 'useremail@gmail.com', 'psasword' => 1234];

 public function checkAttempt($credentials)
 {
     $user = UserTable::whereRaw('lower(email) = ? ', [$credentials['email']])->firstorFail();

     $validPassword = Hash::check($credentials['password'], $user->password;

     if ($user  && $validPassword)
     {
        // set user session over here
     }
 }
vishal ribdiya
  • 1,013
  • 2
  • 12
  • 20
0

@btl Answers is perfect, but there is an another way.

Yout can edit the register controller.

App/Http/Controller/Auth/RegisterController.php

protected function create(array $data)
{
   return User::create([
       'name' => $data['name'],
       'email' => strtolower($data['email']),
       'password' => bcrypt($data['password']),
   ]);
}
Levente Otta
  • 713
  • 6
  • 17
0

I wanted to keep the case (of username in my case) the way it was specified in the database. The only way I could find to do this was to override the UserProvider (specifically the retrieveByCredentials part). That was quite a hassle for what seemed to be a simple change. In the end, instead of using the Auth::attempt method, I solved this by manually retrieving the user, checking the password, then using the Auth::login method:

$user = User::where('username', 'ilike', $req->input('username'))->first();

if (!$user ||
    !Hash::check($req->input('password'), $user->password)
) {
    return response()->json(['status' => 'FAILED']);
}
Auth::login($user);
khartnett
  • 831
  • 4
  • 14
0

I solved ovveriding credentials function in LoginController

function credentials(Request $request)
{
    return ["email"=> strtolower($request->email), "password"=> request('password')];
}
cesare
  • 2,098
  • 19
  • 29