ASP.NET Core 3.1 - Security Stamp

This article will describe the implementation of a security stamp property for a user. I will assume you have downloaded the ASP.NET Core 3.1 - Users Without Identity Project or created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.

Users Without Identity Project and Article Series

UWIP v2 implements a SecurityStamp property for the AppUser.

Entities > AppUser:
[Required]
[StringLength(32)]
[Display(Name = "Security Stamp")]
public string SecurityStamp { get; set; }

When a new AppUser is created, the SecurityStamp is set with a GenerateSecurityStamp method.

Services > TokenService:
public string GenerateSecurityStamp()
{
    byte[] bytes = new byte[20];
    RandomNumberGenerator.Fill(bytes);
    return Base32.ToBase32(bytes);
}

The project implements a CreateUserPrincipalAsync function in SignInService.cs. The ClaimsPrincipal employs a security identifier claim (ClaimTypes.Sid) with the SecurityStamp value.

Services > SignInService:
private async Task<ClaimsPrincipal> CreateUserPrincipalAsync(int appUserId, string authenticationMethod)
{
    var appUser = await _userService.GetAppUserByIdAsync(appUserId).ConfigureAwait(false);
    var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, appUser.Id.ToString(), ClaimValueTypes.Integer),
            new Claim(ClaimTypes.Name, appUser.LoginName),
            new Claim(ClaimTypes.Sid, appUser.SecurityStamp),
            new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod),
            new Claim(UWIPConstants.TwoFactorEnabledClaimType, appUser.TwoFactorEnabled.ToString())
        };

    if (appUser.AdministratorRole)
        claims.Add(new Claim(ClaimTypes.Role, UWIPConstants.AdministratorRole));
    if (appUser.MustChangePassword)
        claims.Add(new Claim(UWIPConstants.MustChangePasswordClaimType, string.Empty));

    var applicationIdentity = new ClaimsIdentity(claims, UWIPConstants.ApplicationScheme);

    return new ClaimsPrincipal(applicationIdentity);
}

The CookieValidator has been updated to validate the current SecurityStamp against the ClaimsPrincipal's ClaimTypes.Sid claim.

Services > Middleware > CookieValidator:
private static async Task<bool> ValidateCookieAsync(CookieValidatePrincipalContext context)
{
    var claimsPrincipal = context.Principal;
    var nameIdentifier = claimsPrincipal.Claims?
        .Where(c => c.Type == ClaimTypes.NameIdentifier)
        .Select(c => c.Value)
        .FirstOrDefault();

    if (!int.TryParse(nameIdentifier, out int appUserId)) return false;

    var securityStamp = claimsPrincipal.Claims?
        .Where(c => c.Type == ClaimTypes.Sid)
        .Select(c => c.Value)
        .FirstOrDefault();

    if (string.IsNullOrEmpty(securityStamp)) return false;

    var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
    return await userService.ValidateAppUserSecurityStampAsync(appUserId, securityStamp).ConfigureAwait(false);
}

The SecurityStamp is also used to generate and validate email confirmation and password reset tokens.

Update 02/23/2021

I updated the article links.

Ken Haggerty
Created 02/20/21
Updated 02/23/21 23:39 GMT

Log In or Reset Quota to read more.

Successfully completed. Thank you for contributing.
Processing...
Something went wrong. Please try again.
Contribute to enjoy content without advertisments.
You can contribute without registering.

Comments(0)

Loading...
Loading...

Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?