Peter Hedberg
Peter Hedberg

Reputation: 3649

Problems with Identity.TwoFactorRememberMe and updated user SecurityStamp

After signing in using two-factor with authenticator app, I set rememberClient to true like this:

await _signInManager.TwoFactorAuthenticatorSignInAsync(code: request.Code,
                                                       isPersistent: request.IsPersistent,
                                                       rememberClient: request.RememberClient);

Signing in works fine and I get the .AspNetCore.Identity.Application and Identity.TwoFactorRememberMe cookies. If I sign out and in again I don't need to use two-factor. So long everything is fine.

The problem is when I do some changes in the user, like the phone number, and the SecurityStamp is changed. After the change is made I use await _signInManager.RefreshSignInAsync(user). But the Identity.TwoFactorRememberMe cookie isn't updated. This results in two problems:

  1. The next time I sign in I have to use the two-factor authentication again.
  2. During the same session, if I check if the user has remembered the browser, using await _signInManager.IsTwoFactorClientRememberedAsync(user), it will result in an error "Failed to validate a security stamp" and the .AspNetCore.Identity.Application will be removed.

I've tried to renew the Identity.TwoFactorRememberMe cookie at the same time as the .AspNetCore.Identity.Application cookie, like this:

await base.RefreshSignInAsync(user);
await RememberTwoFactorClientAsync(user);

It works, but it will also set the Identity.TwoFactorRememberMe cookie for those who didn't have it before. I can't check if it is set before, because then I get the error I described in (2) above.

The next thing I will try is to do something like this for every place I do something which changes the user SecurityStamp:

var isTwoFactorClientRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);

// do the changes...

await _signInManager.RefreshSignInAsync(user);
if (isTwoFactorClientRememberedAsync)
    await _signInManager.RememberTwoFactorClientAsync(user);

Is there something I'm missing here, or is this the only way to go?

I'm using IdentityServer4 and a SPA app, but I don't believe that has anything to do with the problem.

Upvotes: 3

Views: 1267

Answers (1)

Peter Hedberg
Peter Hedberg

Reputation: 3649

I ended up adding a method in my custom ApplicationSignInManager:

public async Task<TResult> KeepSignInAsync<TResult>(ApplicationUser user, Func<Task<TResult>> func)
{
    var isTwoFactorClientRemembered = await IsTwoFactorClientRememberedAsync(user);

    var result = await func();

    await RefreshSignInAsync(user);

    if (isTwoFactorClientRemembered)
        await RememberTwoFactorClientAsync(user);

    return result;
}

When I change something which will update the user SecurityStamp I use it like this:

var result = await _signInManager.KeepSignInAsync(user, () => _userManager.SetPhoneNumberAsync(user, phoneNumber));

Upvotes: 2

Related Questions