Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Single user login in MVC 4 applictaion

I am implementing an application where there is a mechanic for each machine in the company using the application. I'm trying to implement a user policy whereby if user is in a role "Mechanic" -- with username Machine1, and is logged in for that machine, only one user can be logged at at the same time with the Machine1 username for the company. If someone else tries to login with the same username, it should be blocked and informed that there is already logged in user. When the session timeout expires i should log out the logged in user and free the login to be used. The same happens when the user logouts by himself. I'm trying to build this on asp.net MVC 4 application.

I've thought of using the SessionId, UserId and IsLoggedIn boolean in the database. But in this case i need to change the logged in flag on session timeout in the MVC app to write in the database, which seems overkill if many users are logged in.

What would the implementation be like? What methods or attributes should I be using to handle the session management in the database ?

FYI

I have made my own method where i check if the user is logged in, here it is:

public static bool ValidateUser(string username, string password, string companyName)
{
    int? companyId = myRepository.GetCompanyId(companyName);

    int? userId = companyId == 0 ? null : myRepository.GetUserId(username, companyId);

    if (userId.HasValue && userId.Value != 0)
    {
        var userKey = Security.GenerateUserKey(username, companyName);
        return WebSecurity.Login(userKey, password);
    }
    else
    {
        return false;
    }
}

Here in this method i can check somehow if the session id is the same as in the database.

like image 258
dlght Avatar asked Dec 18 '13 13:12

dlght


People also ask

What is SSO in MVC?

Single Sign-On (SSO) makes this process easier by using the same authentication ID and authorizing the user across multiple services. Because of SSO, as the name suggests, the user is required to sign in only once in a certain time window before the authentication token expires.


1 Answers

The crux of the problem for this issue is knowing when the user logged out to allow the next user in with the same name. I do not know of a precise way to do this in a web application, but here is a method that can approximate knowing when the user logged out by controlling how long their login lasts. For testing this I set the timeout to 1 minute so I could quickly test going between the different users with the same user name. You control this through the web.config.

<forms loginUrl="~/Account/Login" timeout="1" slidingExpiration="false" />

Notice that I set slidingExpiration to false to know precisely when they will be logged out. If you use a sliding expiration there is no way to predict when the logout actually occurred because you cannot count on the user physically logging out.

To keep track of what user is currently logged in I used a MemoryCache and used its expiration policy to automatically track the time for me. Here is a simple helper class I wrote to manage this.

public class UserManager
{
    public static bool IsLoggedIn(string username)
    {
        ObjectCache cache = MemoryCache.Default;
        return !string.IsNullOrEmpty(cache[username] as string);

    }

    public static void SetToLoggedIn(string username)
    {
        ObjectCache cache = MemoryCache.Default;
        CacheItemPolicy policy = new CacheItemPolicy();
        //Expires after 1 minute.
        policy.AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(1));
        cache.Set(username, username, policy);

    }
}

Notice that I used AbsoluteExpiration and set the timeout to the same length I set for forms authentication. This clears the object from the cache in 1 minute from the time it is written. That way I can just check for the presence of the object in the cache to determine if the allotted time for a users login has passed. I use the username as the key for the cached object since we are checking that one user with that username is in the system at any one time.

Now we just change our login action to look like this.

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult Login(LoginModel model, string returnUrl)
    {
        if (ModelState.IsValid )
        {
            if (UserManager.IsLoggedIn(model.UserName))
            {
                ModelState.AddModelError("", "A user with that user name is already logged in.");
                return View(model);
            }
            if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
            {
                UserManager.SetToLoggedIn(model.UserName);
                return RedirectToLocal(returnUrl);
            }
        }

        // If we got this far, something failed, redisplay form
        ModelState.AddModelError("", "The user name or password provided is incorrect.");
        return View(model);
    }

I tested this out on an MVC 4 application using SimpleMembership and it works. The only downside I can see with this approach is that you will require an absolute timeout instead of sliding. Getting the correct setting for the timeout will be critical to reduce frustration of users being logged out at specific intervals and to minimize the time another user will have to wait to get logged in.

like image 197
Kevin Junghans Avatar answered Sep 20 '22 12:09

Kevin Junghans