How do I prevent a user Id from only being able to log in to one computer at a time? An example, user "Sam" logs in to his Desktop. User "Sam" also logs in from their laptop. Now I need the session on the desktop killed or what I want to happen is to make the old token invalid on the desktop.
I'm using dotnet 5.0
Would you need a column called currentlyloggedinUser in the "users" table in the database to see if they have logged in using Boolean?
Would you also need a security stamp?
Below is the login method which is placed in the AuthController
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(UserForLoginDto userForLoginDto)
{
try
{
// does the user exist in the database
var user = await _userManager.FindByNameAsync(userForLoginDto.Username);
// does the password match
var result = await _signInManager.CheckPasswordSignInAsync(user, userForLoginDto.Password, true);
//if the users are logged in, store True Value in the database
user.nonconcurrent = true;
var noLoggedin = await _userManager.UpdateAsync(user);
if(!result.IsLockedOut)
{
if(user.IsEnabled)
{
if (result.Succeeded)
{
var userToReturn = _mapper.Map<UserForReturnDto>(user);
return Ok(new
{
token = GenerateJwtToken(user).Result,
user = userToReturn,
});
}
return Unauthorized("Login Failed"); //if username and password are incorrect return unauthorised
}
return Unauthorized("This account is disabled. Please get an administrator to unlock this account.");
}
return Unauthorized("This account is locked out. Please try again in 10 minutes or get an " +
"administrator to unlock your account.");
}
catch (ArgumentNullException)
{
return Unauthorized("Login Failed");
}
}
Below is part of the StartUp.cs
namespace Schedular.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// services.AddDbContext<DataContext>(x => x.UseMySql(Configuration
// .GetConnectionString("DefaultConnection")));
services.AddDbContext<DataContext>(x => x.UseMySql(Configuration
.GetConnectionString("DefaultConnection"),
new MySqlServerVersion(new Version(8, 0, 21)),
mySqlOptions => mySqlOptions
.CharSetBehavior(CharSetBehavior.NeverAppend)));
var lockoutOptions = new LockoutOptions()
{
AllowedForNewUsers = true,
DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10),
MaxFailedAccessAttempts = 5
};
// for role and identity authentication
// initial creation of user in the user table in database
IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
{
// password requirements
opt.Lockout = lockoutOptions;
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<DataContext>();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
// allows api to use authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
});
// authorisation policy
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAccess", policy => policy.RequireRole("Admin"));
options.AddPolicy("everyone", policy => policy.RequireRole("Admin", "standard"));
});
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddNewtonsoftJson(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
services.AddScoped<ITaskScheduleRepository, TaskScheduleRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<INotesRepository, NotesRepository>();
services.AddScoped<IAttachmentFileRepository, AttachmentFileRepository>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<IReportRepository, ReportRepository>();
services.AddControllers();
services.AddCors();
//allows use of tokens
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new AutoMapperProfiles());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
}
This is the user.cs model file
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace Schedular.API.Models
{
public class User: IdentityUser<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
// to disable account and have a time limiter on when it can be enabled again
public bool nonconcurrent { get; set; }
// connect User table to the userRole join table. the below configure the relationship
public virtual ICollection<UserRole> UserRoles {get; set;}
}
}
Below is the userForLoginDto
namespace Schedular.API.Dtos
{
public class UserForLoginDto
{
public string Username { get; set; }
public string Password { get; set; }
}
}