#auth::attempt using a different guard and different email and password column name

48 messages · Page 1 of 1 (latest)

pliant star
#

I'm at my wit's end here. I know the forms are sending the correct data since if I dd($request->all()) it's sending the correct data, however it keeps saying the credentials are incorrect. I already deleted the default users table Lavarel gives you and the mss_client_accounts table does not exist so I know it's interacting with the correct table but I don't know why it's still not authenticating properly. I checked the database multiple times for mismatched column names, seeded different hashed passwords and randomized emails and it's still not authenticating correctly.

What I want to do is separate the User and Admin controllers so I just copy pasted the controllers and logic that came with Laravel Breeze and resuited it for my use, but I'm a complete beginner to Laravel here and I already got stuck here and I don't know how to progress - been at this for like 2 days, I tried various solutions from google but none of them worked. Please help me.

#

auth::attempt using a different guard and different email and password column name

#

I forgot to send the config/auth.php. It's relevant.

pliant star
#

Oh...

#

I thought it would be shorter this way. I'll redo it.

#

This is the model I have of mss_admin_accounts

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class mss_admin_account extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $primaryKey = 'Admin_ID';
    protected $guard_name = 'admin';


    protected $fillable = [
        'Admin_ID',
        'Admin_Name',
        'Admin_Email',
        'Admin_Password',
    ];

    protected $hidden = [
        'Admin_Password',
    ];

    protected $casts = [
        'Admin_Password' => 'hashed',
    ];
}
#

This is the AdminLoginController

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\AdminLoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class AdminLoginController extends Controller
{
    public function create(): View
    {
        return view('admin.login');
    }

    public function adminLogin(AdminLoginRequest $request): RedirectResponse
    {
        $request->authenticate();
        $request->session()->regenerate();

        return redirect()->intended(RouteServiceProvider::HOMEADMIN);
    }

    public function destroy(Request $request): RedirectResponse
    {
        Auth::guard('admin')->logout();

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

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

        return redirect('/');
    }
}
#

This is the AdminLoginRequest

<?php

namespace App\Http\Requests\Auth;

use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class AdminLoginRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }
    public function rules(): array
    {
        return [
            'Admin_Email' => ['required', 'string', 'email'],
            'Admin_Password' => ['required', 'string'],
        ];
    }


    public function authenticate(): void
    {
        $this->ensureIsNotRateLimited();

        if (! Auth::guard('admin')->attempt($this->only('Admin_Email', 'Admin_Password'), $this->boolean('remember'))) {
            RateLimiter::hit($this->throttleKey());

            throw ValidationException::withMessages([
                'Admin_Email' => trans('auth.failed'),
            ]);
        }

        RateLimiter::clear($this->throttleKey());
    }


    public function ensureIsNotRateLimited(): void
    {
        if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
            return;
        }

        event(new Lockout($this));

        $seconds = RateLimiter::availableIn($this->throttleKey());

        throw ValidationException::withMessages([
            'Admin_Email' => trans('auth.throttle', [
                'seconds' => $seconds,
                'minutes' => ceil($seconds / 60),
            ]),
        ]);
    }


    public function throttleKey(): string
    {
        return Str::transliterate(Str::lower($this->input('Admin_Email')).'|'.$this->ip());
    }
}
#

These are the forms in my login blade

                <input style="width:350px;" id="email" type="email" type="email" name='Admin_Email' placeholder="Email" required />
                <x-input-error style="width:350px;" :messages="$errors->get('Admin_Email')"/>
                </div>
                
                <div>
                <input style="width:350px;" id="password" type="password" type="password" name='Admin_Password' placeholder="Password" required />
                <x-input-error style="width:350px;" :messages="$errors->get('Admin_Password')"/>
#

This is the config/auth.php

<?php

return [
    'defaults' => [
        'guard' => 'client',
        'passwords' => 'mss_client_accounts',
    ],

    'guards' => [
        'client' => [
            'driver' => 'session',
            'provider' => 'clients',
        ],
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
        'clients' => [
            'driver' => 'eloquent',
            'model' => App\Models\mss_client_account::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\mss_admin_account::class,
        ],
    ],

    'passwords' => [
        'clients' => [
            'provider' => 'mss_client_accounts',
            'table' => 'password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
        'admins' => [
            'provider' => 'mss_admin_account',
            'table' => 'password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    'password_timeout' => 10800,

];
#

That should be all of them. Sorry for breaking the rules.

spare tundra
#

Someone had problems with double-hashed passwords the other day – so make sure you're not hashing anywhere else (other than that cast). Can't see the code to create users here

pliant star
#

I read that actually, and did your recommendations but it still not working. From what I understood from googlefu, the auth::attempt matches your plaintext password with the hashed password in the database. So, when I tried dd(request->all()) the password is in plain text before the auth::attempt is done so it couldn't be the double hashing unless I'm understanding incorrectly.

Me not writing code to create users is intentional, I was using a seeder for it. I'm focusing more on the login authentication. If it helps, this is the seeder I have

    public function run(): void
    {
        DB::table('mss_admin_accounts')->insert([
            'Admin_Name' => Str::random(10),
            'Admin_Email' => Str::random(10).'@gmail.com',
            'Admin_Password' => Hash::make('password'),
        ]);
    }
spare tundra
#

I meant double hashing when storing the password.
Auth::attempt/login always takes the plaintext password

#

The cast seems to be written to not hash again if already hashed, but if that's not working, you are double hashing (both in seeder and in cast)

#

Or, I see Eloquent is not used for the seeder, so that should be fine

pliant star
#

Yeah... and I didn't have the public function that hashes it like the person having the problem did...

spare tundra
#

I don't know your use case, but I'd recommend reconsidering if you really need another user table than the default

pliant star
#

I've tried it with role-based authentication where I made the default user table have a role_id column integer where 0 is admin and 1 is client and it works, but I'm really stubborn and I want to see if this is really possible. The guides in youtube all sample role-based, but there are sources though not tutorials that says custom guards are possible.

#

I'm not experienced enough to know when to do role-based and multiguard, and I want to broaden my options should the option where role-based is not possible comes up.

spare tundra
#

You could add some debugging (e.g. dd) in SessionGuards attempt() method to see if it's pulling the right data

pliant star
#

Well, I see the Admin_ID should not be there since I set that to auto_incremental, but what about the rest?

#

I'm not seeing it..

#

It matches the one I have in the database and the fields seems to be correct.

#

Okay. I'll try.

#

Still a no. I'm afraid.

#

I placed it under the protected casts

    protected $casts = [
        'Admin_Password' => 'hashed',
    ];

    public function getAuthPassword()
    {
        return $this->Admin_Password;
    }
#

If placement matters.

spare tundra
#

If the model is called AdminAccounts, why prefix all columns with Admin_?

pliant star
#

Since there's going to be another table that's named Client, that's going to have the Client_ prefix too.

#

I'll try to rename the Admin_Password to password in the database and the relevant fields to password see if that works.

#

OH. It worked.

#
true // app\Http\Requests\Auth\AdminLoginRequest.php:48

It FINALLY returned TRUE when I dd for the request.

#

But, is there no way to change that behavior? The getAuthPassword didn't seem to work.

spare tundra
#

In validateCredentials in EloquentUserProvider, the password field on the sent credentials seems hardcoded

#

I.e. your form field name must be password, but you can still use whatever column name you want by overriding that function

pliant star
#

What do you mean by that? The database name having Admin_Password name but the fields in the controllers are "password" ?

spare tundra
#

That the form data has password

#

So what you pass to attempt must be keyed password, if I'm not missing somthing

pliant star
#

I renamed my database's password column back to Admin_Password, the model has Admin_Password like before

    public function rules(): array
    {
        return [
            'Admin_Email' => ['required', 'string', 'email'],
            'password' => ['required', 'string'],
        ];
    }
        dd(Auth::guard('admin')->attempt($this->only('Admin_Email', 'password')));

This attempt returned flase

               <div>
                <input style="width:350px;" id="password" type="password" type="password" name='password' placeholder="Password" required />
                <x-input-error style="width:350px;" :messages="$errors->get('password')"/>
                </div>

This is the form in login blade.

spare tundra
#

Alright, then I'm out of guesses for now

pliant star
#

Well, I guess it's not that much of a loss if I can't rename the password column.

#

Maybe I'll just use an "as" keyword in the read queries.

#

The main goal was to make multiauth happen with different tables anyway.

steep belfry
#

@pliant star Why are you just renaming columns, and using non-standard naming conventions in the first place?

#

It’s better to stick to Laravel’s conventions. Especially if you’re new to the framework. Otherwise you don’t know if you’re dealing with bugs and wrong naming; you’re just making things much more difficult for yourself for the sake of it.

pliant star
#

I wanted to make it easier for me to read since I thought renaming by prefixing it with their level of authentication Client_ and Admin _ would help me not be confused when writing queries for those both tables.