Asked  2 Years ago    Answers:  5   Viewed   123 times

I'm trying to implement a user policy whereby only one user can login at a time. I'm trying to build this on top of Laravel's Auth driver.

I've thought of using the Session driver to store the sessions in the database and make the keys constant for each username. This is probably a terrible implementation because of session fixation.

What would the implementation be like? What methods in the Auth driver should I be editing? Where would the common session key be stored?

 Answers

3

I recently did this.

My solution was to set the session value when a user logs in. Then I had a small class checking if the session ID stored is the same as the current user who is logged in.

If the user logs in from somewhere else the session ID in the DB will update and the "older" user will be logged out.

I didn't alter the Auth driver or anything, just put it on top when the user logs in. Below happens when login is successful:

$user->last_session = session_id();
$user->save();

To check if the session is valid I used below

if(session_id() != Auth::user()->last_session){
   Auth::logout();
   return true;
}

As you can see I added a column in the users table called last_session

Saturday, September 3, 2022
3

Short answer: YOU CAN'T

The session can be destroyed when the entire browser is closed by simply setting expire_on_close in config/session.php (also make sure you clear the cookies in your browser for this to work):

'expire_on_close' => true,

But there is no way to detect when only a tab is closed in the browser. The closest thing you would have is the JavasSript method onbeforeunload that triggers when you close a tab. But it also triggers when you navigate away from a page or hit the back button, and there's no way to differentiate between those actions.


You could set a very short session time on the server, and "ping" the server from any open page, to let it know to keep the session still in use, which would mean it would very quickly expire when the tabs that have the app open are closed, but that's an extremely ugly solution.

Wednesday, October 26, 2022
1

The problem was that I mapped my passport routes in my RouteServiceProvider and not in my AuthServiceProvider

Wednesday, December 14, 2022
 
4

Is this a correct approach?

There are basically 2 ways.

  1. Store the HttpSession handle in the application scope by the user ID as key so that you can get a handle of it and invalidate it. This may work for a small web application running on a single server, but may not work on a web application running on a cluster of servers, depending on its configuration.

    I would only store it in another map in the application scope, not directly in the application scope like as you did, so that you can easier get an overview of all users and that you can guarantee that an arbitrary user ID won't clash with an existing application scoped managed bean name, for example.

  2. Add a new boolean/bit column to some DB table associated with the user which is checked on every HTTP request. If the admin sets it to true, then the session associated with the request will be invalidated and the value in the DB will be set back to false.


how do I clean up my application map?

You could use HttpSessionListener#sessionDestroyed() for this. E.g.

public void sessionDestroyed(HttpSessionEvent event) {
    User user = (User) event.getSession().getAttribute("user");
    if (user != null) {
        Map<User, HttpSession> logins = (Map<User, HttpSession>) event.getSession().getServletContext().getAttribute("logins");
        logins.remove(user);
    }
}
Tuesday, December 6, 2022
5

When I get this issue, the solution that I found to get my project working was to generate a new token with data from older token on each new request.

My solution, that works for me, is bad, ugly, and can generate more issues if you have many async requests and your API(or business core) server is slow.

For now is working, but I will investigate more this issue, cause after 0.5.3 version the issue continues.

E.g:

Request 1 (GET /login):

Some guest data on token

Request 2 (POST /login response):

User data merged with guest data on old token generating a new token

Procedural code example(you can do better =) ), you can run this on routes.php out of routes, I say that is ugly haha:

// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) {
    if($authToken === null) {
         $authToken = JWTAuth::parseToken();
    }
    return $authToken;
};

$getLoggedUser = function() use ($getAuthToken) {
    return $getAuthToken()->authenticate();
};

$getAuthPayload = function() use ($getAuthToken) {
    try {
        return $getAuthToken()->getPayload();
    } catch (Exception $e) {
        return [];
    }
};

$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) {
    $currentPayload = [];
    try {
        $currentAuthPayload = $getAuthPayload();
        if(count($currentAuthPayload)) {
            $currentPayload = $currentAuthPayload->toArray();
        }
        try {
            if($user = $getLoggedUser()) {
                $currentPayload['user'] = $user;
            }
            $currentPayload['isGuest'] = false;
        } catch (Exception $e) {
            // is guest
        }
    } catch(Exception $e) {
        // Impossible to parse token
    }

    foreach ($customPayload as $key => $value) {
        $currentPayload[$key] = $value;
    }

    return $currentPayload;
};

// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try {
    $getLoggedUser();
    $payload = ['isGuest' => false];
} catch (Exception $e) {
    $payload = ['isGuest' => true];
}

try {
    $payload = $mountAuthPayload($payload);
} catch (Exception $e) {
    // Make nothing cause token is invalid, expired, etc., or not exists.
    // Like a guest session. Create a token without user data.
}

Some route(simple example to save user mobile device):

Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) {
    Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) {
        $Response = new IlluminateHttpResponse();
        $user = $getLoggedUser();

        // code to save on database the user device from current "session"...

        $payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
        $token = JWTAuth::encode($payload);
        $Response->header('Authorization', 'Bearer ' . $token);

        $responseContent = ['setted' => 'true'];

        $Response->setContent($responseContent);
        return $Response;
    });
});
Wednesday, September 21, 2022
 
xtoddx
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 

Browse Other Code Languages