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
Creation Series
- ASP.NET Core 3.1 - Users Without Identity
- ASP.NET Core 3.1 - User Entity
- ASP.NET Core 3.1 - Password Hasher
- ASP.NET Core 3.1 - User Management
- ASP.NET Core 3.1 - Admin Role
- ASP.NET Core 3.1 - Cookie Validator
- ASP.NET Core 3.1 - Concurrency Conflicts
- ASP.NET Core 3.1 - Must Change Password
- ASP.NET Core 3.1 - User Database Service
- ASP.NET Core 3.1 - Rename Related Entities
2FA Series
- ASP.NET Core 3.1 - 2FA Without Identity
- ASP.NET Core 3.1 - 2FA User Tokens
- ASP.NET Core 3.1 - 2FA Cookie Schemes
- ASP.NET Core 3.1 - 2FA Authenticating
- ASP.NET Core 3.1 - 2FA Sign In Service
- ASP.NET Core 3.1 - 2FA QR Code Generator
- ASP.NET Core 3.1 - Admin 2FA Requirement
- ASP.NET Core 3.1 - 2FA Admin Override
Enhanced User Series
- ASP.NET Core 3.1 - Enhanced User Without Identity
- ASP.NET Core 3.1 - 2FA Recovery Codes
- ASP.NET Core 3.1 - Login Lockout
- ASP.NET Core 3.1 - Created And Last Login Date
- ASP.NET Core 3.1 - Security Stamp
- ASP.NET Core 3.1 - Token Service
- ASP.NET Core 3.1 - Confirmed Email Address
- ASP.NET Core 3.1 - Password Recovery
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.
Comments(0)