User Management
Manage users, accounts, passwords, and authentication through commands.
User Lifecycle
Create User
↓
Set/Reset Password
↓
Enable Account (if disabled)
↓
User Authenticates
↓
Lock/Unlock Account (on failed attempts)
↓
Delete/Disable Account (optional)
User Creation
Create Internal User
Create a user with username and password:
var command = new CreateUserCommand(
emailAddress: "john.doe@example.com",
firstName: "John",
lastName: "Doe");
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
var userId = result.UserId;
// User created but not yet verified
}
What happens:
- User aggregate created with unique ID
- Status set to unverified
- No password set yet
- Account is enabled
- No roles assigned
Create User with Password
Create user and set password immediately:
var command = new CreateUserWithPasswordCommand(
emailAddress: "jane.smith@example.com",
firstName: "Jane",
lastName: "Smith",
password: "SecurePassword123!",
isVerified: false);
var result = await commandRunner.ExecuteAsync(command);
Create User from Registration
User self-registers via signup form:
var command = new CreateUserFromRegistrationCommand(
emailAddress: "new@example.com",
firstName: "New",
lastName: "User",
password: "UserChosenPassword1!");
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
// Send verification email
// User clicks link to verify account
}
Create External User
User signs in via external provider (Entra ID):
var command = new CreateExternalUserCommand(
externalUserId: "entra-user-id",
externalProviderType: "Entra",
emailAddress: "external@example.com",
firstName: "External",
lastName: "User");
var result = await commandRunner.ExecuteAsync(command);
What happens:
- User created from external identity
- Email and profile populated from provider
- User is automatically verified (external provider verified them)
- Can also set internal password if desired
Create User with External Identity
Create internal user linked to external provider:
var command = new CreateUserWithExternalCommand(
emailAddress: "dual@example.com",
firstName: "Dual",
lastName: "Auth",
externalUserId: "entra-id",
externalProvider: "Entra");
var result = await commandRunner.ExecuteAsync(command);
// User can now sign in with:
// - Entra ID (external)
// - Password (if password set later)
Account Status Management
Verify Account
Confirm user's email address:
var command = new RequestAccountVerificationCommand(userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// Email sent to user with verification link
// User clicks link with token
// Link calls:
var verifyCommand = new VerifyAccountAndSetPasswordCommand(
userId: userId,
verificationToken: tokenFromEmail,
password: "NewPassword123!");
var verifyResult = await commandRunner.ExecuteAsync(verifyCommand);
Disable Account
Prevent user from signing in (don't permanently delete):
var command = new DisableAccountCommand(userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// User.IsDisabled = true
// User cannot authenticate
// Account data preserved
Enable Account
Re-activate disabled account:
var command = new EnableAccountCommand(userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// User.IsDisabled = false
// User can sign in again
Delete User (Hard Delete)
Permanently remove user (use caution):
// Not directly exposed - typically requires admin audit
// Implement in application layer with logging
private async Task DeleteUserPermanentlyAsync(Guid userId)
{
var user = await userRepository.FindOne(
new ByIdSpecification<User>(userId));
if (user.HasValue)
{
userRepository.Delete(user.Value);
await unitOfWork.SaveEntitiesAsync();
// Log deletion for audit trail
}
}
Password Management
Change Password
User changes their own password:
var command = new ChangePasswordCommand(
userId: currentUser.Id,
currentPassword: userEnteredCurrentPassword,
newPassword: userEnteredNewPassword);
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
// Password updated
// Previous passwords stored in history (cannot reuse)
}
else
{
// Current password incorrect or new password invalid
}
Initiate Password Reset
Start password reset flow (user forgot password):
// User enters email on "Forgot Password" page
var command = new InitiatePasswordResetCommand(
emailOrUserPrincipalName: "john.doe@example.com");
var result = await commandRunner.ExecuteAsync(command);
// Email sent with reset link containing token
Complete Password Reset
User clicks reset link and enters new password:
var command = new PasswordResetCommand(
resetToken: tokenFromEmail,
newPassword: userEnteredPassword);
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
// Password reset complete
// User can now sign in with new password
}
Force Override Password
Admin sets user's password without knowing current:
var command = new ForceOverridePasswordCommand(
userId: targetUserId,
newPassword: "TemporaryPassword123!");
var result = await commandRunner.ExecuteAsync(command);
// User must change password on next login
Set Password During Authentication
Set initial password during account verification:
var command = new SetPasswordDuringAuthenticationCommand(
userId: userId,
password: userEnteredPassword);
var result = await commandRunner.ExecuteAsync(command);
Account Locking
Lock Account
Prevent sign-in after failed attempts (auto or manual):
var command = new LockAccountCommand(userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// User.IsLocked = true
// Automatic unlock after configured duration
// Or manual unlock by admin
Unlock Account
Admin unlock:
var command = new UnlockAccountCommand(userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// User.IsLocked = false
// User can sign in again
Auto-unlock: Configure in internal provider settings:
{
"providers": {
"internal": {
"accountLockout": {
"maxFailedAttempts": 5,
"lockoutDurationMinutes": 15
}
}
}
}
After 15 minutes, account automatically unlocks.
User Identifiers
User Principal Name (UPN)
Alternative identifier (username) for user:
// Add UPN
var addCommand = new AddUserPrincipalNameCommand(
userId: userId,
userPrincipalName: "john.doe");
var result = await commandRunner.ExecuteAsync(addCommand);
// User can now sign in with:
// - Email: john.doe@example.com
// - UPN: john.doe
Change UPN
Update user's username:
var command = new InitiateUserPrincipleNameCommand(
userId: userId,
newUserPrincipalName: "j.doe");
var result = await commandRunner.ExecuteAsync(command);
// Email sent for verification
// User clicks link
var verifyCommand = new VerifyUserPrincipleNameCommand(
userId: userId,
verificationToken: tokenFromEmail);
await commandRunner.ExecuteAsync(verifyCommand);
// New UPN active
// Old UPN still works for revert window (default: 48 hours)
Revert UPN
Undo UPN change within revert window:
var command = new RevertUserPrincipleNameChangeCommand(
userId: userId);
var result = await commandRunner.ExecuteAsync(command);
// UPN reverted to previous value
// Must be within emailChangeRevertWindowHours
Disable UPN
Prevent sign-in via specific UPN:
var command = new DisableUserPrincipalNameCommand(
userId: userId,
userPrincipalName: "oldusername");
var result = await commandRunner.ExecuteAsync(command);
// Cannot sign in with oldusername
// Can still sign in with email or other UPNs
Delete UPN
Remove UPN completely:
var command = new DeleteUserPrincipalNameCommand(
userId: userId,
userPrincipalName: "unused");
var result = await commandRunner.ExecuteAsync(command);
Profile Management
Update Profile
User updates their name:
var command = new UpdateProfileCommand(
userId: currentUser.Id,
firstName: "Jonathan",
lastName: "Doe");
var result = await commandRunner.ExecuteAsync(command);
Update Core Details
Admin updates user details:
var command = new UpdateUserCoreDetailsCommand(
userId: targetUserId,
emailAddress: "newemail@example.com",
firstName: "Updated",
lastName: "Name");
var result = await commandRunner.ExecuteAsync(command);
Update Metadata
Store custom key-value data on user:
var command = new UpdateMetaItemsCommand(
userId: userId,
metaItems: new Dictionary<string, string?>
{
["DepartmentId"] = "dept-123",
["ManagerId"] = "user-456",
["CostCenter"] = "accounting",
["StartDate"] = "2024-03-01"
});
var result = await commandRunner.ExecuteAsync(command);
// Metadata searchable/filterable
// Use for application-specific data
Roles & Permissions
Set User Privileges
Assign roles to user:
var command = new SetUserPrivilegesCommand(
userId: targetUserId,
roles: new[]
{
"Administrator",
"ContentEditor",
"Reviewer"
});
var result = await commandRunner.ExecuteAsync(command);
// User now has these roles
// Use in [Authorize(Roles = "Administrator")]
Authentication History
Track user login attempts:
var authHierarchy = await readModels.AuthenticationHistory
.Where(h => h.UserId == userId)
.OrderByDescending(h => h.AttemptTime)
.Take(10)
.ToListAsync();
foreach (var attempt in authHierarchy)
{
Console.WriteLine($"{attempt.AttemptTime} - {attempt.Result}");
// 2024-03-02 14:30:00 - Success
// 2024-03-02 14:25:00 - Failed - Invalid password
// 2024-03-02 12:00:00 - Success
}
External Users
Update External User
Sync profile from external provider:
var command = new UpdateProfileFromExternalCommand(
userId: userId,
externalProviderType: "Entra",
firstName: "UpdatedFromEntra",
lastName: "User",
emailAddress: "newemail@example.com");
var result = await commandRunner.ExecuteAsync(command);
// User profile synced with provider
Sign In External User
User signs in via external provider:
var command = new SignInExternalUserCommand(
externalUserId: "entra-id",
externalProviderType: "Entra");
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
var userId = result.UserId;
// User authenticated
}
else
{
// User not found in system
// Offer account creation flow
}
Bulk Operations
Create Multiple Users
var users = new[]
{
new { Email = "user1@example.com", FirstName = "User", LastName = "One" },
new { Email = "user2@example.com", FirstName = "User", LastName = "Two" },
new { Email = "user3@example.com", FirstName = "User", LastName = "Three" }
};
var createdUsers = new List<Guid>();
foreach (var userData in users)
{
var command = new CreateUserCommand(
emailAddress: userData.Email,
firstName: userData.FirstName,
lastName: userData.LastName);
var result = await commandRunner.ExecuteAsync(command);
if (result.Success)
{
createdUsers.Add(result.UserId);
}
}
// Send bulk verification emails
foreach (var userId in createdUsers)
{
var requestCommand = new RequestAccountVerificationCommand(userId);
await commandRunner.ExecuteAsync(requestCommand);
}
Disable Multiple Users
var userIds = new[] { userId1, userId2, userId3 };
foreach (var userId in userIds)
{
var command = new DisableAccountCommand(userId);
await commandRunner.ExecuteAsync(command);
}
Best Practices
-
Email Validation
- Require email verification before account use
- Send re-verification after email change
- Queue verification emails, don't block request
-
Password Policy
- Enforce complexity (uppercase, lowercase, numbers, special)
- Prevent password reuse (stored in history)
- Clear expiration (or risk-based reauth)
- Min 8 characters, max 128 characters
-
Account Lockout
- Auto-lock after 5 failed attempts
- 15-minute lockout duration
- Email user on lock
- Allow admin unlock
-
Audit Trail
- Log all user modifications
- Track password changes
- Record admin actions
- Monitor failed sign-ins
-
Cleanup
- Disable unused accounts after 90 days
- Remove unverified accounts after 30 days
- Archive deleted users for compliance
Troubleshooting
User Can't Sign In After Password Reset
Check:
- Reset token hasn't expired (typically 24 hours)
- Password meets complexity requirements
- Account isn't disabled or locked
Email Verification Not Received
- Check spam folder
- Verify user's email address is correct
- Check email service is running
- Resend verification email
UPN Already Exists
UPN must be unique per system:
- Choose different username
- Delete old UPN first
- Check current UPNs via query
Role Assignment Not Effective
- Verify user actually has role
- Check authorization policy is correct
- Restart app if role cache stale
- Check role name casing
Next Steps
- Authentication Overview - Understand system architecture
- MFA Setup - Configure multi-factor authentication
- External Authentication - Link external identity providers