Build the mental model
This project pulls together three lessons: passkeys-explained, password-vs-passkey, and account-recovery-security. Each taught one piece — what a passkey is and why it resists phishing, why it beats even a strong password, and why the recovery path (codes, backup email/phone) is often the weakest link an attacker goes after.
Cybersecurity Basics' personal-security-audit project scores general password/2FA/uniqueness hygiene — this project does not repeat that. It asks a narrower, more advanced question: can you move past passwords to a passkey, and would your recovery path hold up against an attacker?
- List your important accounts
- Enable a passkey where supported, or a unique strong password otherwise
- Enable 2FA as a backstop
- Secure your recovery codes offline and access-controlled
- Review the recovery email and phone number tied to the account
- Review the list of devices the account currently trusts
PASSKEY AND RECOVERY MIGRATION PLAN
-----------------------------------
PASSKEY AND RECOVERY MIGRATION PLAN
------------------------------------
IMPORTANT ACCOUNTS (email, banking, work/social)
|
v
PASSKEY SUPPORTED? --no--> UNIQUE STRONG PASSWORD
| |
yes |
v v
2FA ENABLED <------------------
|
v
RECOVERY CODES SECURED (offline, access-controlled)
|
v
RECOVERY EMAIL/PHONE REVIEWED (still yours, secure)
|
v
TRUSTED DEVICES REVIEWED (unknown devices removed)
|
v
MIGRATION PLAN COMPLETE FOR THIS ACCOUNTConnect it to a real scenario
Work through this project as a live migration plan, not a one-time checklist. List three or four accounts that would hurt the most if compromised — primary email, banking, and a primary social/work account. For each account, walk the same six-stage sequence.
Passkey or strong password
Check whether the service offers passkey sign-in; enable it and keep the password only as a fallback. If no passkey option exists, confirm the password is unique and never reused.
Enable 2FA
Use an authenticator app or hardware key rather than SMS where that option exists, as a backstop even after enabling a passkey.
Secure recovery codes
Generate fresh recovery codes, store them offline and access-controlled, and delete any old copies kept insecurely.
Review recovery email/phone
Confirm you still control both the recovery email and phone number, and that the recovery email is not itself a weakly secured account.
Review trusted devices
Open the account's trusted-devices or active-sessions list and remove anything unrecognized.
Record the result for each account in a short table, then run the plan-builder function against the same data and compare its prioritized output to your own judgment.
Try the working example
function buildMigrationPlan(accounts) {
function scoreAccount(acc) {
let score = 0;
const plan = [];
if (!acc.passkeyEnabled) {
plan.push(
acc.hasStrongUniquePassword
? "Enable a passkey where supported (current password is already strong and unique)"
: "Enable a passkey, or set a unique strong password if passkeys are not supported yet"
);
score += acc.hasStrongUniquePassword ? 2 : 4;
}
if (!acc.twoFAEnabled) {
plan.push("Enable 2FA (prefer an authenticator app or hardware key over SMS)");
score += 4;
}
if (!acc.recoveryCodesSecured) {
plan.push("Generate fresh recovery codes and store them offline, access-controlled");
score += 3;
}
if (!acc.recoveryContactVerified) {
plan.push("Verify the recovery email/phone is current and itself well-secured");
score += 3;
}
if (!acc.trustedDevicesReviewed) {
plan.push("Review trusted devices / active sessions and remove unrecognized ones");
score += 2;
}
return {
account: acc.name,
importance: acc.importance,
riskScore: score * acc.importance,
actionsNeeded: plan.length,
plan,
};
}
return accounts
.map(scoreAccount)
.sort((a, b) => b.riskScore - a.riskScore)
.map((acc, i) => ({ priority: i + 1, ...acc }));
}
// Describe accounts by their CURRENT setup -- never real credentials, just booleans.
const accounts = [
{
name: "Primary Email",
importance: 3,
passkeyEnabled: false,
hasStrongUniquePassword: true,
twoFAEnabled: true,
recoveryCodesSecured: false,
recoveryContactVerified: false,
trustedDevicesReviewed: false,
},
{
name: "Banking",
importance: 3,
passkeyEnabled: false,
hasStrongUniquePassword: true,
twoFAEnabled: true,
recoveryCodesSecured: true,
recoveryContactVerified: true,
trustedDevicesReviewed: true,
},
{
name: "Work / Primary Social",
importance: 2,
passkeyEnabled: false,
hasStrongUniquePassword: false,
twoFAEnabled: false,
recoveryCodesSecured: false,
recoveryContactVerified: false,
trustedDevicesReviewed: false,
},
];
const plan = buildMigrationPlan(accounts);
for (const item of plan) {
console.log(`#${item.priority} ${item.account} (risk score ${item.riskScore}, ${item.actionsNeeded} actions)`);
for (const step of item.plan) console.log(` - ${step}`);
}The function returns a priority list ranked by risk score: 'Work / Primary Social' is #1 (risk score 32, 5 actions), 'Primary Email' is #2 (risk score 30, 4 actions), and 'Banking' is #3 (risk score 6, 1 action) — only its passkey step remains, matching the console output above.5-minute try-it
List three or four of your own important accounts (without ever writing real passwords, recovery codes, or other credentials into this lesson) using the same account object shape as the code — passkeyEnabled, hasStrongUniquePassword, twoFAEnabled, recoveryCodesSecured, recoveryContactVerified, trustedDevicesReviewed — and run buildMigrationPlan on them. Compare the generated priority order to what you would have guessed.
One important caution
Treating this as a one-time project instead of revisiting it whenever you open a new account or a service adds passkey support
Securing recovery codes and 2FA but leaving the recovery email itself protected by only a weak, reused password — the weakest link just moves one step back
FIDO Alliance — Passkeys — Digital Privacy & Modern Security