Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

shivkhaira's avatar

Laravel Multiple user logged at same time

In my website I have two roles admin and member. The admin has is able to login as any member by clicking a button - done using "Auth::onceUsingID" and it all works fine. My new requirement is that when a admin clicking to login as member, the member dashboard should open in new tab and in the current tab the admin should also be logged in (two different user logged at same time). But after searching online I was not able to find a clear solution, some suggested making different tables for different roles etc. So I thought the way I can do is put same site in two different folder on server with same database so that each site can function separate from each other - one for admin and other for member and when admin wants to login as member transfer the admin to the other folder site thus having two different user logged at same time. Is there any better solution regarding this issue?

0 likes
7 replies
martinbean's avatar
Level 80

@shivkhaira Don’t use separate tables just for the sake of different roles. Just store roles against your users and then use them to determine what they can and cannot do in your application. I go into this in more depth here: https://martinbean.dev/blog/2021/07/29/simple-role-based-authentication-laravel/

Being able to log in as any member is called user impersonation. You will need an endpoint (that is really secure) that puts the current user ID in the session, and then authenticates you in as the target user. It can be as simple as:

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

// Save ID of current user (admin) in session
$request->session()->impersonator($request->user()->getKey());

// Now log in as requested user
Auth::loginUsingId($userId);

Put a banner at the top of the site saying “Impersonating [user name]” just so no one forgets and starts doing things whilst logged in as that user such as submitting content, placing orders, etc.

To stop impersonating, have another endpoint that re-authenticates the original user if one exists in the session:

// Get original user ID before session is regenerated
$userId = $request->session()->pull('impersonator');

// If there was no impersonator in session,
// user is maybe trying to do something they shouldn’t
if ($userId === null) {
    Auth::logout();

    return redirect()->to('/');
}

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

// Restore admin session
Auth::loginUsingId($userId);
shivkhaira's avatar

I have already done all this but the problem is when I login using "loginUsingId" the admin is no longer the logged user unless the Impersonation session (which is expected). I want to know can i have two users logged in at same time that is admin and member.

martinbean's avatar

I want to know can i have two users logged in at same time that is admin and member.

@shivkhaira No. How would that even work? When you visit a page, how would Laravel know to check if you want to view that page as the impersonated user or as the “original” user?

Just switch the session as above when impersonating the user. When you want to stop impersonating, switch back to the original user. Don‘t try and mix two distinct sessions.

shivkhaira's avatar

Thank you after taking a second look and reading your process I managed to solve my problem.

Theraloss's avatar

An idea could be this: when you click to authenticate as a user, you open a new tab with a query parameter that will be the User ID encrypted and then, with a middleware, you authenticate every request as a user if that query param is present.

Then in your URLs (e.g. /profile etc.) you're going to forward that query param if it's present in the current request. Depending on how are you building the internal links there may be different ways to do this.

// YourController.php

use Illuminate\Support\Facades\Crypt;

public function impersonate(int $userId): RedirectResponse
{
	// Check if it's an admin etc. etc.

	// Redirect as user
	return redirect('/dashboard?_u=' . Crypt::encryptString($userId));
}

Now there's a problem: this is easily vulnerable to replay attack: if I get the URL and paste it into my browser I could be that user. After all the security checks (e.g. I'm an admin), one simple way to solve it could be to hash the user id with a timestamp and give it a grace period. For example:

// YourController.php

use Illuminate\Support\Facades\Crypt;

public function impersonate(int $userId): RedirectResponse
{
	// Check if it's an admin etc. etc.

	// Redirect as user
	return redirect('/dashboard?_u=' . Crypt::encryptString(time() . ':' . $userId));
}

Then in your middleware, when you decrypt the value, you're going to check for that timestamp too and when you forward this query param, you have to recalculate the hash with a fresh timestamp.

// ImpersonateUser.php

use Closure;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;

class ImpersonateUser
{
    public function handle($request, Closure $next)
    {
        // Early return if no _u query param
        if (! ($impersonate === $request->query('_u'))) {
            return $next($request);
        }

        // Check if the logged in user is an admin
        if (!Auth::check() || !Auth::user()->can('impersonate')) {
            return $next($request);
        }

        try {
            [$timestamp, $userId] = explode(':', $impersonate);

            // Check for grace period (1 minute)
            if (now()->diffInMinutes(Carbon::createFromTimestamp($timestamp)) >= 1) {
                throw new PreconditionFailedHttpException();
            }

            Auth::onceUsingId($userId);
        } catch (Exception $e) {
            throw new PreconditionFailedHttpException();
        }

        return $next($request);
    }
}

If you decide to adopt this solution, please be 200% sure that you covered all the security issue that this could lead to.

martinbean's avatar

Yeah, don’t add sensitive data to URLs, even if it is encrypted or time-sensitive. This opens us two attack vectors:

  1. URLs are opaque strings end up in far more places than you initially think. Server logs, chats after an accidental paste, etc.
  2. If a bad actor works out these IDs are encrypted, and they have a known plaintext value, they can now make efforts to start reversing your encryption key.

Stick with the session-based approach.

2 likes
Theraloss's avatar

Yeah this was an idea, that's the reason of the final disclaimer :) A temporarily token should definitely be a better solution, although not 100% secure. Anyway because the crypt is done by the APP_KEY, I find it very hard to reverse it (until they don't have access to the project ENVs, but in that case the problem would be a bigger one).

The session solution unfortunately does not seem to solve the issue (as per the answer above) though.

Please or to participate in this conversation.