how to restrict x amount of login on each client app in specific the SPA client with grant type - implicit
This is out of scope within Identity server
Solut
implement profile service overrride activeasync
public override async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await userManager.FindByIdAsync(sub);
//Check existing sessions
if (context.Caller.Equals("AccessTokenValidation", StringComparison.OrdinalIgnoreCase))
{
if (user != null)
context.IsActive = !appuser.VerifyRenewToken(sub, context.Client.ClientId);
else
context.IsActive = false;
}
else
context.IsActive = user != null;
}
startup
services.AddTransient();
while adding the identity server service to collection under configure services
.AddProfileService();
Update
Session.Abandon(); //is only in aspnet prior versions not in core
Session.Clear();//clears the session doesn't mean that session expired this should be controlled by addSession life time when including service.
I have happened to found a better way i.e. using aspnetuser securitystamp, every time user log-in update the security stamp so that any prior active session/cookies will get invalidated.
_userManager.UpdateSecurityStampAsync(_userManager.FindByEmailAsync(model.Email).Result).Result
Update (final):
On sign-in:-
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberLogin, false);
if (result.Succeeded)
{
//Update security stamp to invalidate existing sessions
var user = _userManager.FindByEmailAsync(model.Email).Result;
var test= _userManager.UpdateSecurityStampAsync(user).Result;
//Refresh the cookie to update securitystamp on authenticationmanager responsegrant to the current request
await _signInManager.RefreshSignInAsync(user);
}
Profile service implementation :-
public class ProfileService : ProfileService
{
public override async Task IsActiveAsync(IsActiveContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Subject == null) throw new ArgumentNullException(nameof(context.Subject));
context.IsActive = false;
var subject = context.Subject;
var user = await userManager.FindByIdAsync(context.Subject.GetSubjectId());
if (user != null)
{
var security_stamp_changed = false;
if (userManager.SupportsUserSecurityStamp)
{
var security_stamp = (
from claim in subject.Claims
where claim.Type =="AspNet.Identity.SecurityStamp"
select claim.Value
).SingleOrDefault();
if (security_stamp != null)
{
var latest_security_stamp = await userManager.GetSecurityStampAsync(user);
security_stamp_changed = security_stamp != latest_security_stamp;
}
}
context.IsActive =
!security_stamp_changed &&
!await userManager.IsLockedOutAsync(user);
}
}
}
*
Hook in the service collection:-
*
services.AddIdentityServer()
.AddAspNetIdentity()
.AddProfileService();
i.e. on every login, the security stamp of the user gets updated and pushed to the cookie, when the token expires, the authorize end point will verify on the security change, If there is any then redirects the user to login. This way we are ensuring there will only be one active session