Skip to main content

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

  1. Email Validation

    • Require email verification before account use
    • Send re-verification after email change
    • Queue verification emails, don't block request
  2. 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
  3. Account Lockout

    • Auto-lock after 5 failed attempts
    • 15-minute lockout duration
    • Email user on lock
    • Allow admin unlock
  4. Audit Trail

    • Log all user modifications
    • Track password changes
    • Record admin actions
    • Monitor failed sign-ins
  5. 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