Implicit grant SPA with identity server4 concurren

2019-02-21 01:17发布

问题:

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

Solutions tried -

  1. Access tokens persisted to DB, however this approach the client kept updating the access token without coming to code because the client browser request is coming with a valid token though its expired the silent authentication is renewing the token by issues a new reference token ( that can be seen in the table persistGrants token_type 'reference_token')

  2. Cookie event - on validateAsync - not much luck though this only works for the server web, we can't put this logic on the oidc library on the client side for SPA's.

  3. Custom signInManager by overriding SignInAsync - but the the executing is not reaching to this point in debug mode because the IDM kept recognising the user has a valid toke ( though expired) kept re issueing the token ( please note there is no refresh token here to manage it by storing and modifying!!!)

Any clues how the IDM re issue the token without taking user to login screen, even though the access token is expired??(Silent authentication. ??

回答1:

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<IProfileService, ProfileService>();

while adding the identity server service to collection under configure services

 .AddProfileService<ProfileService>();

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<ApplicationUser>

{
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<ApplicationUser>()                
         .AddProfileService<ProfileService>();

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