Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impersonating only works if both users have the same password #162

Open
Arne1303 opened this issue Jul 4, 2022 · 37 comments
Open

Impersonating only works if both users have the same password #162

Arne1303 opened this issue Jul 4, 2022 · 37 comments

Comments

@Arne1303
Copy link

Arne1303 commented Jul 4, 2022

Using
laravel/framework: 9.17

We've been using the package for quite a while without problems but it looks like it broke in a recent dependency update.

When initiating an impersonation (using the route defined in Route::impersonate()) the user gets logged out and redirected to login, the impersonation does however work if both users (the impersonating and the one being impersonated) have the same password.

Does someone else also experience this behavior/is there a known workaround?
Thanks!

@XternalSoft
Copy link

Hello,

Same problem but since I used the "auth.session" middleware. If you use only "auth" middleware then no problem appears.

@Arne1303 Arne1303 closed this as completed Jul 7, 2022
@adantart
Copy link

Same problem here, and I'm using sanctum ... any workaround ?

@Arne1303
Copy link
Author

@adantart I got it working by removing the password from the session when leaving the impersonation, haven't got any problems but still use with caution:

Add a new Session guard:

<?php

namespace App\Auth;

class SessionGuard extends \Lab404\Impersonate\Guard\SessionGuard
{
    /**
     * @inheritDoc
     */
    public function quietLogout()
    {
        parent::quietLogout();

        foreach (array_keys(config('auth.guards')) as $guard) {
            $this->session->remove('password_hash_' . $guard);
        }
    }
}

Use the new Session guard in AuthServiceProvider (should already exist):

class AuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        /** @var AuthManager $auth */
        $auth = $this->app['auth'];

        $auth->extend(
            'session',
            function (Application $app, $name, array $config) use ($auth) {
                $provider = $auth->createUserProvider($config['provider']);
                return new \App\Auth\SessionGuard($name, $provider, $app['session.store']);
            });
    }
}

@MarceauKa
Copy link
Member

@Arne1303 Interested to make a PR?

@Arne1303
Copy link
Author

@MarceauKa Sure! Should be there by Monday

Arne1303 added a commit to Arne1303/laravel-impersonate that referenced this issue Jul 24, 2022
Arne1303 added a commit to Arne1303/laravel-impersonate that referenced this issue Jul 24, 2022
Arne1303 added a commit to Arne1303/laravel-impersonate that referenced this issue Jul 25, 2022
@eriktobben
Copy link

Hi @MarceauKa When do you think this PR will be merged? :)

@adantart
Copy link

@Arne1303 ...

I got this error
App\Providers\AuthServiceProvider::App\Providers{closure}(): Argument #1 ($app) must be of type App\Providers\Application, Illuminate\Foundation\Application given, called in

pointing to your line:
function (Application $app, $name, array $config) use ($auth) {

@Arne1303
Copy link
Author

@adantart Looks like you importer the wrong Application class, you need to change the imposer or if it is used somewhere else specify it just for that function.

You can also remove the type hint completely, that one works 2.

@adantart
Copy link

I don't understand ...
I created the first piece of code in app folder Auth/SessionGuard.php
and then I add the second piece in the boot() function of my AuthServiceProvider, now it's like this:

<?php

namespace App\Providers;

use App\Models\Team;
use App\Policies\TeamPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Team::class => TeamPolicy::class,
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //


        /** @var AuthManager $auth */
        $auth = $this->app['auth'];

        $auth->extend(
            'session',
            function (Application $app, $name, array $config) use ($auth) {
                $provider = $auth->createUserProvider($config['provider']);
                return new \App\Auth\SessionGuard($name, $provider, $app['session.store']);
            });
    }
}

@adantart
Copy link

Ok, I noticed the Application namespace ... and I added

\Illuminate\Foundation\Application $app, ...

@adantart
Copy link

Ok, I tested it , but it stills logs out when I "leave impersonation"

@adantart
Copy link

Ok, working !!!

But I had to use my leave-impersionation method (I mean, not using the route('impersonate.leave') provided by the library, and put it in a "non admin middleware" scope, if not the "impersonate user" had no access to the method.

But yes, now it's working ...

in a certain way ... :-P

@writehow
Copy link

@adantart can you share your code please?
I can't get it working :(

@Arne1303 can you please assist?

I have create the directory App\Auth with file SessionGuard.php

`<?php

namespace App\Auth;

class SessionGuard extends \Lab404\Impersonate\Guard\SessionGuard
{
/**
* @inheritdoc
*/
public function quietLogout()
{
parent::quietLogout();

    foreach (array_keys(config('auth.guards')) as $guard) {
        $this->session->remove('password_hash_' . $guard);
    }
}

}`

My AuthServiceProvider looks like :

`public function boot()
{
$this->registerPolicies();

    //


    /** @var AuthManager $auth */
    $auth = $this->app['auth'];

    $auth->extend(
        'session',
        function (\Illuminate\Foundation\Application $app, $name, array $config) use ($auth) {
            $provider = $auth->createUserProvider($config['provider']);
            return new \App\Auth\SessionGuard($name, $provider, $app['session.store']);
        });
}`

Route

Route::middleware(['auth:sanctum','verified','authadmin'])->group(function(){
Route::get('/impersonate/user/{user_id}', 'App\Http\Controllers\ImpersonateController@index')->name('impersonate');

@Arne1303
Copy link
Author

@writehow Can you check if youre custom SessionGuard is being used? My first guess would be cache, try php artisan optimize:clear

If it still doenst work I created a PR (#163) which does replace the password hashes with the ones from the new user instead of scrubing them of, you could try to adapt that one

@writehow
Copy link

@Arne1303 Thank you for the quick reply !
I will try it immediately and will let you know.

@adantart
Copy link

adantart commented Sep 29, 2022

@Arne1303 (and @writehow) Your code works ... but it has a side effect:
no events of Auth (or sessionguard) are fired.

If I use your code, the bug we are talking here is fixed, ok
but no events fired.

If I comment the code, Auth events works perfectly

@mrpritchett
Copy link

I have the same issue with the side effect. I also found another side effect. Stopping impersonation now logs the user out completely instead of returning to the original user.

@Arne1303
Copy link
Author

@adantart @mrpritchett I've used slightly different code in my pr #163 can you check if the side effects are also present there?

@adantart
Copy link

You mean this patch, right ?
a306583

@Arne1303
Copy link
Author

Yes, there are 2 versions, one is further up in this comment thread and one is the patch submitted, which one did you use?

@mrpritchett
Copy link

I'm still getting logged out with that PR when I try to leave impersonation.

@adantart
Copy link

Sorry for taking so long to answer :-P

So ... @Arne1303 ... the last one works.

SUMMARY:

In your first solution (#162 (comment)) your modifications involved

  • to extend the session through the boot function of AuthServiceProvider
  • to remove the password_hash_ in the quietLogout

As I said, this patched the problem, but affected to the events of Auth, since they were not triggered properly.

In your second solution (a306583) your modifications involved:

  • to update the password hash in the quietLogin

This second solution is cleaner than the first one ;-) and also allow Auth events to be triggered.

For example I had this in my EventServiceProvider:

class EventServiceProvider extends ServiceProvider
{
    //...

    protected $listen = [
        Login::class => [ \App\Listeners\Login::class ],
        Logout::class => [ \App\Listeners\Logout::class ],
        PasswordReset::class => [ \App\Listeners\PasswordReset::class ],
 
   //...
}

since I do some stuff when Login, Logout, ... are performed.

Now they are working well, and impersonation works perfectly although users have same or different password.

THANK YOU !

@mrpritchett
Copy link

@Arne1303 - I'm still seeing a log out when leaving impersonation.

@adantart
Copy link

The error has "arrived" again after some update in the last week :-(
I don't know if the problem is 1.7.4 ... but again, only impersonates correctly when users have the same password :-(

@mrpritchett
Copy link

@adantart I can confirm this issue is still unresolved for me as well.

@adantart
Copy link

@adantart I can confirm this issue is still unresolved for me as well.

Weird thing here is that it was "fixed" for me, until a week ago or so ... maybe an update :-(

@BigBlockStudios
Copy link

I am getting this issue as well, impersonating a user logs the user out, Laravel 10.10.1 Jetstream/Fortify

@tonypartridge
Copy link

public function boot()
    {
        // Build out the impersonation event listeners - Otherwise we get a redirect to login if not setting the password_hash_sanctum when using sanctum.
        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_web');
            session()->put([
                'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });
    }
    ```
    
    Try the above in the EventServiceProvider

@BigBlockStudios
Copy link

public function boot()
    {
        // Build out the impersonation event listeners - Otherwise we get a redirect to login if not setting the password_hash_sanctum when using sanctum.
        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_web');
            session()->put([
                'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });
    }
    ```
    
    Try the above in the EventServiceProvider

Thanks - yes that works just fine! :)

@tonypartridge
Copy link

Great!

@Natorator
Copy link

This worked, but it may bear pointing out one has to add the required use declarations to the top of EventServiceProvider.php

use Auth;
use Lab404\Impersonate\Events\TakeImpersonation;
use Lab404\Impersonate\Events\LeaveImpersonation;

Otherwise, it fails //silently//.

@warmwhisky
Copy link

warmwhisky commented Aug 12, 2023

public function boot()
    {
        // Build out the impersonation event listeners - Otherwise we get a redirect to login if not setting the password_hash_sanctum when using sanctum.
        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_web');
            session()->put([
                'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });
    }
    ```
    
    Try the above in the EventServiceProvider

Unfortunately this does not work for me using Laravel 10.18 and PHP 8.2.4
Any ideas? It actually worked the first time I used it but all subsequent attempts redirected to the login page.

Edit: From this page https://laracasts.com/discuss/channels/laravel/jetstream-login-as-user?page=1&replyId=779264
I created a seperate route group for the impersonate route and changed the middleware auth:sanctum to auth:web not ideal but for now it is working as I need

OrangeJuiced added a commit to OrangeJuiced/laravel-impersonate that referenced this issue Sep 14, 2023
@nickolasjadams
Copy link

public function boot()
    {
        // Build out the impersonation event listeners - Otherwise we get a redirect to login if not setting the password_hash_sanctum when using sanctum.
        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_web');
            session()->put([
                'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });
    }
    ```
    
    Try the above in the EventServiceProvider

Perfect! Thank you so much!
Closes #186

@brycematheson
Copy link

brycematheson commented Dec 27, 2023

Unfortunately, none of the above comments helped me. After I updated my EventServiceProvider.php with the following, however, the issue disappeared.

    public function boot()
    {
        Event::listen(function(\Lab404\Impersonate\Events\TakeImpersonation $event) {
            session()->put('password_hash_sanctum', $event->impersonated->getAuthPassword());
        });
    }

@shokanshi
Copy link

Event::listen(function(\Lab404\Impersonate\Events\TakeImpersonation $event) {
session()->put('password_hash_sanctum', $event->impersonated->getAuthPassword());
});

I am using auth:web middleware and this is what worked for me for EventServiceProvider

public function boot()
    {
        
        // Build out the impersonation event listeners - Otherwise we get a redirect to login if not setting the password_hash_sanctum when using sanctum.
        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_web' => $event->impersonated->getAuthPassword(),
            ]);

            Auth::setUser($event->impersonated);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->put([
                'password_hash_web' => $event->impersonator->getAuthPassword(),
            ]);

            Auth::setUser($event->impersonator);
        });
    }

Note that both Take and Leave Impersonation event requires Auth::setUser() in order to work for me.

@feyton
Copy link

feyton commented Apr 30, 2024

      Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_web');
            session()->put([
                'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });

This worked for me to resolve this issue.
But I am struggling when it comes to implementing this in Vue JS

@pkeogan
Copy link

pkeogan commented Oct 8, 2024

this might help

        Event::listen(function (TakeImpersonation $event) {
            session()->put([
                'password_hash_'.Auth::getDefaultDriver() => $event->impersonated->getAuthPassword(),
            ]);
        });

        Event::listen(function (LeaveImpersonation $event) {
            session()->remove('password_hash_'.Auth::getDefaultDriver());
            session()->put([
                'password_hash_'.Auth::getDefaultDriver() => $event->impersonator->getAuthPassword(),
            ]);
            Auth::setUser($event->impersonator);
        });

that mirrors whats going on in UpdatePasswordForm for Jetstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests