14

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.

3
  • 1
    I had a similar problem. I used this solution: stackoverflow.com/questions/34696134/laravel-global-scope-auth Create two separate User models. The first doesn't have the global scope, and is used for just authentication. The second extends the first, includes the global scope, and is used for everything else. Commented Apr 21, 2017 at 19:43
  • 1
    Correct me if I'm wrong but haven't you answered your own question here by using withoutGlobalScope? Commented Jun 22, 2018 at 21:28
  • Please refer the link: stackoverflow.com/a/59297932/6196907 Commented Dec 12, 2019 at 5:00

7 Answers 7

1

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
    ]
 ]
Sign up to request clarification or add additional context in comments.

3 Comments

This does not work. $model->withoutGlobalScope('active'); fails because parent::createModel(); returns a user object. withoutGlobalScope must be called on a query, though.
Works perfectly for me - helped avoid Symfony\Component\Debug\Exception\FatalThrowableError: Maximum function nesting level of '256' reached, aborting! error when global scope was applied to user model, which was checking if user was authenticated (and thus booting the user again).
Right, you have to override this function instead newModelQuery()
1
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().

1 Comment

The auth middleware will log out the user automatically, so it is a bad solution
1

@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();
    }
}

Comments

1

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');
}

Comments

0

Extend the AuthController with the code you used in your OP. That should work.

public function postLogin(Request $request)
{
    $user = User::withoutGlobalScope('active')
                ->where('username', $request->username)
                ->first();

    if($user != null){
        if (Hash::check($request->password, $user->password)){
            $user->active = 1;
            $user->save();
        }
    }

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

5 Comments

Does this work for later versions of Laravel? I tried finding 'postLogin' in the documentation, but couldn't find any. Furthermore it seems AuthController is now LoginController and RegisterController.
The problem here is in the auth middleware it will see no auth user so you will be logged out
@TheGeeky I'm not using the auth middleware here
In any other route, The auth middleware will use the scope you made and consider you a guest user
Thats not true - I'm setting $user->active = 1 so that the next time they make a request it considers them an active user and will work with the auth middleware
0

I resolved it by creating the new package.

mpyw/scoped-auth: Apply specific scope for user authentication.

Run composer require mpyw/scoped-auth and modify your User model like this:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Mpyw\ScopedAuth\AuthScopable;

class User extends Model implements UserContract, AuthScopable
{
    use Authenticatable;

    public function scopeForAuthentication(Builder $query): Builder
    {
        return $query->withoutGlobalScope('active');
    }
}

You can also easily pick Illuminate\Auth\Events\Login to activate User on your Listener.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        \Illuminate\Auth\Events\Login::class => [
            \App\Listeners\ActivateUser::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        //
    }
}

 

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;

class ActivateUser
{
    /**
     * Handle the event.
     *
     * @param  Illuminate\Auth\Events\Login $event
     * @return void
     */
    public function handle(Login $event)
    {
        $event->user->fill('active', 1)->save();
    }
}

 

Comments

0

I had to use ->withoutGlobalScopes() instead

in order for it to work

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.