Prevent Multiple Logins in Asp.Net Identity

I have seen a fair amount of questions on Stackoverflow asking how to prevent users sharing their password. Previously with MembershipProvider framework it was not a simple task. People went into all sorts of crazy procedures. One of the most common was to have a static global list of logged-in users. And if a user already in that list, the system denied their second login. This worked to an extent, until you clean cookies in the browser and try to re-login.

Luckily now Asp.Net Identity framework provides a simple and clean way of preventing users sharing their details or logging-in twice from different computers.

2-Factor Authentication

One of the most simple ways to prevent password-sharing is make 2-factor authentication mandatory. Only login a person who have received a text message or email with authentication token. It is very unlikely that people will have access to the same mail-box or a mobile phone. So this usually solves the problem pretty well.

I believe there are many tutorials online, including guides from Microsoft. So I will not go into details of implementation.

Problem with this approach is an additional cost involved. Cost of implementation and a cost of SMS provider. And if you have enough users logging-in each day, this can be a pretty penny. So make sure the means are justified.
Another cost is usability – it is always an extra step I need to take when I login when 2FA is enabled on the service.
But consider an additional security measures added to the service. I always enable 2FA on services I rely on. And if 2FA is not available on service, most likely I’ll choose a service with 2FA available

So consider all your pros and cons.

Basic accounts.

A lot of the times 2FA is not available for different reasons. So we need to do something else. Identity framework has something called “Security Stamp” – this is a random string (usually a GUID) that is stored on user record. Idea behind this is to verify that user password have not been changed since the last login: value of security stamp is stored in authentication cookie and with configured interval the framework compares the value in the database to the one in the cookie. This is for the cases when people are logged in in multiple browsers/computers. And if they change a password, all those multiple sessions will be expired.

Now we can use that feature to achieve our goal of preventing multiple logins or password share. Now we need to implement that on every login the security stamp have been changed. So all logged-in sessions will think that the password have been changed and will log-out. The implementation is not very interesting, but a few details are important.

I’m working with default VS2013 MVC project template. If you are using something else – your code may vary, but the idea behind is the same.

You need to modify the login procedure and catch the moment when user have provided correct username and password, but before the cookie is set. And change the security stamp BEFORE user is signed-in. This part of code is usually defined in AccountController in Login action method:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }


    // check if username/password pair match.
    var loggedinUser = await UserManager.FindAsync(model.Email, model.Password);
    if (loggedinUser != null)
    {
        // Now user have entered correct username and password.
        // Time to change the security stamp
        await UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
    }

    // do sign-in AFTER we have done the update of the security stamp, so the new stamp goes into the cookie
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        // do the rest of method

Now there are a few places where user can be logged-in. Look for usages of SignInManager and put code that updates the security stamp before user is signed-in. I would even go further – extend the sign-in manager and override SignInAsync method – do security stamp update there. But this is up to you how to implement.

Next step is to make sure the framework actually compares the security stamps from the authentication cookie with the value in the database. This is pretty easy, there is already a functionality that does it for you, we only need to tweak it slightly.

Go to App_Start\Startup.Auth.cs, you should fine there a part of code that defines cookie authentication and looks like this: app.UseCookieAuthentication(new CookieAuthenticationOptions.......... There is a definition for Provider:

    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }

Note that validateInterval have been set to zero. By default it is 30 minutes, but you need to make this interval small. Effectively this tells the framework to compare security stamps on every request from a user. If you have a large number of users and an extra call to the database is becoming a performance drag, increase the validation interval to a few minutes.

The full cookie configuration should look like this:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
    // other settings
});     

Make sure you have defined AuthenticationType, otherwise the system won’t login users at all – the authentication cookie will not be set without this value.

The whole prevention of password sharing scenario will look like this: User A shares a password with User B. User A logs in, then User B logs in, thus making session of User A expired. User A logs in again and makes session of User B expired. They play this game for a while until they realise they need 2 accounts to use your system and should not share passwords.

This post have been inspired by this QA on Stackoverflow and the topic starter have provided a full source-code of solution on Github