Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel authentication without global scope

In my Laravel app users can disable (not delete) their account to disappear from the website. However, if they try to login again their account should be activated automatically and they should log in successfully.

This is done with "active" column in the users table and a global scope in User model:

protected static function boot() {
    parent::boot();

    static::addGlobalScope('active', function(Builder $builder) {
        $builder->where('active', 1);
    });
}

The problem now is that those inactive accounts can't log in again, since AuthController does not find them (out of scope).

What I need to achieve:

  1. Make AuthController ignore global scope "active".
  2. If username and password are correct then change the "active" column value to "1".

The idea I have now is to locate the user using withoutGlobalScope, validate the password manually, change column "active" to 1, and then proceed the regular login.

In my AuthController in postLogin method:

$user = User::withoutGlobalScope('active')
            ->where('username', $request->username)
            ->first();

if($user != null) {
    if (Hash::check($request->username, $user->password))
    {
        // Set active column to 1
    }
}

return $this->login($request);

So the question is how to make AuthController ignore global scope without altering Laravel main code, so it will remain with update?

Thanks.

like image 377
Trakus Ret Avatar asked Jul 08 '16 12:07

Trakus Ret


4 Answers

@Sasan's answer is working great in Laravel 5.3, but not working in 5.4 - createModel() is expecting a Model but gets a Builder object, so when EloquentUserProvider calls $model->getAuthIdentifierName() an exception is thrown:

BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::getAuthIdentifierName() in /var/www/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2445

Instead, follow the same approach but override more functions so that the right object is returned from createModel().

getQuery() returns the builder without the global scope, which is used by the other two functions.

class GlobalUserProvider extends EloquentUserProvider
{
    /**
     * Get query builder for the model
     * 
     * @return \Illuminate\Database\Eloquent\Builder
     */
    private function getQuery()
    {
        $model = $this->createModel();

        return $model->withoutGlobalScope('active');
    }

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->getQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->first();
    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed  $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {
        $model = $this->createModel();

        return $this->getQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->where($model->getRememberTokenName(), $token)
            ->first();
    }
}
like image 173
Sam Avatar answered Nov 20 '22 01:11

Sam


Sasan Farrokh has a right answer. The only thing not to rewrite createModel but newModelQuery and this will work

protected function newModelQuery($model = null)
{
    $modelQuery = parent::newModelQuery();
    return $modelQuery->withoutGlobalScope('active');
}
like image 37
turalex Avatar answered Nov 20 '22 01:11

turalex


Create a class GlobalUserProvider that extends EloquentUserProvider like below

class GlobalUserProvider extends EloquentUserProvider {

    public function createModel() {
         $model = parent::createModel();
         return $model->withoutGlobalScope('active');
    }

}

Register your new user provider in AuthServiceProvider:

Auth::provider('globalUserProvider', function ($app, array $config) {
     return new GlobalUserProvider($this->app->make('hash'), $config['model']);
});

Finally you should change your user provider driver to globalUserProvider in auth.php config file.

 'providers' => [
    'users' => [
        'driver' => 'globalUserProvider',
        'model' => App\Models\User::class
    ]
 ]
like image 1
Sasan Farrokh Avatar answered Nov 20 '22 01:11

Sasan Farrokh


protected static function boot() 
{
    parent::boot();
    if (\Auth::check()) {
        static::addGlobalScope('active', function(Builder $builder) {
            $builder->where('active', 1);
        });
    }
}

Please try this for login issue, You can activate after login using withoutGlobalScopes().

like image 1
Amit Kumar Avatar answered Nov 19 '22 23:11

Amit Kumar