Lezir Opav
Lezir Opav

Reputation: 709

Auth::attempt return true, Auth::check() return false

I'm using Laravel 9 authentication. It's a successful login, but on the main page, it is not logged in. So I'm expecting it to authenticate.

Login controller

if (Auth::attempt([
    'x_username' => $data['username'],
    'x_user_password' => $data['password']
])) {
    return true; //login success
} else {
    return false;
}

Dashboard controller (it shows 2)

if(Auth::check()) {
    echo "1"; //not happened, but login before is success?
} else {
    echo "2"; 
}

$user = Auth::user();
var_dump($user); //it shows NULL

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 User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $table = 'tbl_x_user';
    
    protected $fillable = [
        'x_username',
        'x_user_email',
        'x_user_password',
    ];

    protected $hidden = [
        'x_user_password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

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

auth.php

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],
'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
],
'password_timeout' => 10800,

kernels.php

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];
}

Notes

Upvotes: 3

Views: 2015

Answers (4)

Abdulla Nilam
Abdulla Nilam

Reputation: 38609

After wasting a couple of hours found a solution for this. The issue cause is

auth()->attempt always check fields such as username, email, password. In your case, you have used a custom column name on the table.

The solution that I found is

  1. Installed two new libraries via composer. (Mappable)
  2. Mapped your custom table fields to standard nameing fields.

Bonus

Now, you can use username to get the field data. Ex:

$user = User::where(['username' => 'ab'])->first();
$user->username(); # bonus

Install Mappable and Eloquence

composer require sofa/eloquence 
composer require sofa/eloquence-mappable

In the User model

Import

use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Mappable;

use

use Eloquence, Mappable;

In model body

protected array $maps = [
    'username' => 'x_username', # I used x_username as my local test 
    'password' => 'x_user_password',
];

In Controller

if (auth()->attempt(['username' => 'ab', 'password' => 'ab@123'])) {
    return view('welcome');
} else {
    return "not logged in";
}

IMPORTANT: Laravel encrypts passwords using Hashing. So make sure your password is hashed in the DB. Because Auth attempt() always works with Hashing


For testing purposes, I used welcome.blade.php.

@auth
    <h3>Logged In</h3>
@else
    <h3>Not yet Logged In</h3>
@endauth

And the output was

enter image description here


Test data

Migration

Schema::create('x_users', static function (Blueprint $table) {
    $table->increments('id');
    $table->string('x_username');
    $table->string('x_user_email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('x_user_password');
    $table->rememberToken();
    $table->timestamps();
});

User create

User::create([
    'x_username' => 'ab',
    'x_user_email' => '[email protected]',
    'x_user_password' => Hash::make('ab@123'),
]);

In model

Used $guarded to ignore MassAssignment error (Not recommended. Just for time save)

protected $table = 'x_users';
protected $guarded = [];

Upvotes: 0

JiFus
JiFus

Reputation: 968

Set up

I've tested it on a new Laravel app with simple Breeze authentication, see below for the set-up used.

$ laravel new testing
$ cd testing
$ composer require laravel/breeze --dev
$ php artisan breeze:install

Then I've updated the users migration like this:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('x_username')->unique();
    $table->string('x_user_email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('x_user_password');
    $table->rememberToken();
    $table->timestamps();
});

Then I've run php artisan migrate:fresh, which will rebuild the database from scratch.

Inside the RegisteredUserController I've made an update to use the new fields you specified:

$user = User::create([
    'x_username' => $request->name,
    'x_user_email' => $request->email,
    'x_user_password' => Hash::make($request->password),
]);

I've updated the model according to what you set in your question.

Then I've registered a new user through visiting /register.

I'm automatically logged in after registering, so I'll logout using the logout link provided in the navigation from Breeze.

Now, inside resources/views/auth/login.blade.php I've changed the email address field to use the username:

<div>
    <x-input-label for="username" value="Username" />

    <x-text-input id="username" class="block mt-1 w-full" type="text" name="username" :value="old('username')" required autofocus />

    <x-input-error :messages="$errors->get('username')" class="mt-2" />
</div>

Actually getting to the login problem.

So now that the project is set up, let's have a look at what isn't working. To log in with the new fields we'll need to change the login code (in Breeze's case the App\Http\Requests\Auth\LoginRequest.

I've changed the authenticate method inside the request as follows:

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

    if (! Auth::attempt([
        'x_username' => $this->input('username'),
        'password' => $this->input('password'),
    ], $this->boolean('remember'))) {
        RateLimiter::hit($this->throttleKey());

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

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

Note that credentials should contain two types of fields:

  1. One or more fields that are unencrypted, and can thus be used to fetch the possible user from the database.
  2. The password field (which is encrypted).

In the example code the field that's used to fetch the user from the database is x_username (Laravel filters out all fields containing the key password, and then uses the others to find a user).

The field used to verify the password is password. You've already specified the getAuthPassword method in your user to make sure that it uses the x_user_password field. Laravel will thus check if the password credential entered can be hashed to the same value as in x_user_password.

As far as I can see this is the only mistake you made: not using the key password in your Auth::attempt() call.

If you have any issues with your session not sticking, ensure that the session cookie is properly sent to the server. Check your browser console under "Storage" and then "Cookies" to see if the cookie was sent to the browser. If the cookie isn't sent, the session will not be recognised, and you will not be logged in.

Sometimes the cookie will not be sent to the server, for example if you set secure => true in session.php (which can be done through SESSION_SECURE_COOKIE=true in your .env). The cookie will then only be sent if you're visiting your app over HTTPS.

Other than the secure setting, the domain and path of course also matter. Since you've said you hadn't touched the session.php configuration I'll assume that there's no issues there.

Upvotes: 0

samurai
samurai

Reputation: 49

Have you added the middleware to the route?

Route::get('/', function () {})->middleware('auth');

or you can do this inside the controller

public function __construct()
{
   $this->middleware('auth');
}

Upvotes: 0

Hamid Raza
Hamid Raza

Reputation: 32

You can try this:

In Blade:

@auth
    // User is Authenticated
@else
    // User is Not Authenticated
@endauth

Upvotes: 0

Related Questions