Implementing Account Management
1. Creating User Accounts
| Field | Validation | Storage |
|---|---|---|
| RFC 5322, lowercased, unique index | VARCHAR(254) | |
| username | 3–30 chars, ^[a-z0-9_]+$ | VARCHAR(30), citext |
| password_hash | Argon2id encoded string | TEXT |
| status | pending|active|locked|deleted | ENUM |
| created_at | UTC timestamp | TIMESTAMPTZ |
Example: Idempotent user creation
@Transactional
public User createUser(SignupRequest req) {
if (userRepo.existsByEmailIgnoreCase(req.email())) {
// Don't reveal existence — send re-confirmation email instead
mailer.sendDuplicateAttempt(req.email());
return null;
}
var hash = passwordEncoder.encode(req.password());
var user = new User(req.email(), hash, Status.PENDING);
userRepo.save(user);
verificationService.sendEmail(user);
return user;
}
2. Validating User Input
| Field | Rule | Library |
|---|---|---|
| Format + MX lookup (optional) | Joi, Zod, Bean Validation | |
| Phone | E.164 format | libphonenumber |
| Names | Unicode letters, length ≤100 | Regex + normalize NFC |
| URLs | Allowed schemes only (http/https) | URL parser, not regex |
| All inputs | Reject control chars, normalize whitespace | OWASP ESAPI |
3. Implementing Email Verification
Email Verification Flow
- User registers → account status =
PENDING - Generate token (32 bytes CSPRNG), store SHA-256 hash + expiry (24h)
- Send link:
https://app/verify?token={raw} - On click: hash token, lookup, check expiry
- Set
email_verified_at = NOW(), status =ACTIVE - Delete verification token row
4. Handling Username Availability Checks
| Concern | Mitigation |
|---|---|
| Enumeration | Rate-limit checks per IP (5/min) |
| Race condition | Use DB unique constraint + retry |
| Reserved names | Blocklist (admin, root, api, ...) |
| Homoglyphs | NFKC normalize + Unicode confusables map |
5. Implementing Profile Management
| Operation | Security Requirement |
|---|---|
| Update email | Re-verify new address before activation |
| Update phone | SMS OTP confirmation |
| Change password | Require current password + re-auth |
| Update MFA | Step-up authentication required |
| Sensitive changes | Email user "your X was changed" |
6. Allowing Account Deletion
| Approach | Behavior | Use Case |
|---|---|---|
| Hard delete | Remove row immediately | GDPR right-to-erasure |
| Soft delete | Set deleted_at, hide from queries | Recoverable, audit |
| Anonymization | Replace PII with hashes/nulls, keep FK integrity | Preserve analytics |
| Grace period | Soft-delete + purge after 30 days | "Account recovery" window |
7. Implementing Account Deactivation
| State | Behavior |
|---|---|
| DEACTIVATED | Cannot login, data preserved |
| Sessions | All revoked immediately |
| Tokens | Added to revocation list |
| Reactivation | Email confirmation + identity proof |
8. Handling Account Recovery
| Recovery Method | Use Case | Risk |
|---|---|---|
| Email link | Lost password | Email account takeover |
| SMS code | Lost password + verified phone | SIM swap |
| Backup codes | Lost MFA device | Code theft |
| Trusted contact | Lost all factors | Social engineering |
| ID verification | High-value account | Privacy + cost |
9. Implementing User Impersonation
| Control | Requirement |
|---|---|
| Permission | Explicit support:impersonate role |
| Token claims | Include act (actor) claim per RFC 8693 |
| UI indicator | Persistent banner "Acting as ..." |
| Audit | Log start, end, every action with both IDs |
| Scope | Restrict (e.g., no password change) |
| Duration | Short-lived token (≤15 min) |
Example: JWT with actor claim (RFC 8693)
{
"sub": "user_42",
"act": { "sub": "support_agent_7", "email": "agent@acme.com" },
"scope": "read profile orders",
"exp": 1715000000
}
10. Auditing Account Changes
| Event | Fields Logged |
|---|---|
| create | user_id, ip, ua, source (web/api) |
| update | field_name, old, new, actor_id |
| delete | user_id, actor_id, reason |
| role_change | added/removed roles, actor_id |
| password_change | timestamp, ip (NEVER the password) |