[{"data":1,"prerenderedAt":1807},["ShallowReactive",2],{"guide-password-security":3},{"id":4,"title":5,"body":6,"date":1799,"description":1800,"extension":1801,"meta":1802,"navigation":208,"path":1803,"readingTime":356,"seo":1804,"stem":1805,"__hash__":1806},"guides\u002Fguides\u002Fpassword-security.md","Password Security Best Practices for 2026",{"type":7,"value":8,"toc":1761},"minimark",[9,14,18,21,25,28,106,110,118,125,130,135,138,143,156,162,359,364,429,433,436,553,582,586,650,654,661,665,668,689,693,696,738,742,745,901,907,911,915,926,930,962,966,969,977,980,991,995,998,1002,1087,1091,1199,1203,1206,1220,1224,1284,1288,1305,1309,1313,1316,1545,1551,1555,1558,1590,1594,1597,1623,1627,1641,1645,1649,1666,1670,1687,1691,1746,1750,1757],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"Password security remains the primary line of defense for user accounts. Despite advances in authentication technology, weak password storage and handling practices continue to cause major data breaches.",[15,19,20],{},"This guide covers the current best practices for password security in 2026, including secure hashing algorithms, proper salt usage, multi-factor authentication, and organizational policies.",[10,22,24],{"id":23},"the-current-threat-landscape","The Current Threat Landscape",[15,26,27],{},"Before diving into practices, understand what we are defending against:",[29,30,31,47],"table",{},[32,33,34],"thead",{},[35,36,37,41,44],"tr",{},[38,39,40],"th",{},"Threat",[38,42,43],{},"Description",[38,45,46],{},"Impact",[48,49,50,62,73,84,95],"tbody",{},[35,51,52,56,59],{},[53,54,55],"td",{},"Brute force attacks",[53,57,58],{},"Trying every possible password combination",[53,60,61],{},"Fast with modern GPUs",[35,63,64,67,70],{},[53,65,66],{},"Dictionary attacks",[53,68,69],{},"Trying common passwords from lists",[53,71,72],{},"Affects 30%+ of users",[35,74,75,78,81],{},[53,76,77],{},"Credential stuffing",[53,79,80],{},"Using leaked passwords from other breaches",[53,82,83],{},"Massive due to prior breaches",[35,85,86,89,92],{},[53,87,88],{},"Rainbow tables",[53,90,91],{},"Precomputed hash lookup tables",[53,93,94],{},"Mitigated by proper salting",[35,96,97,100,103],{},[53,98,99],{},"Phishing",[53,101,102],{},"Tricking users into revealing passwords",[53,104,105],{},"Bypasses all technical defenses",[10,107,109],{"id":108},"password-hashing-never-store-plain-text","Password Hashing: Never Store Plain Text",[15,111,112,113,117],{},"The golden rule: ",[114,115,116],"strong",{},"never store passwords in plain text",". If your database is compromised, plain-text passwords give attackers immediate access to every account.",[15,119,120,121,124],{},"Instead, store ",[114,122,123],{},"password hashes"," — one-way cryptographic representations that cannot be reversed.",[126,127,129],"h3",{"id":128},"recommended-hashing-algorithms-2026","Recommended Hashing Algorithms (2026)",[131,132,134],"h4",{"id":133},"_1-argon2-recommended","1. Argon2 (Recommended)",[15,136,137],{},"Winner of the Password Hashing Competition (2015), Argon2 is the current gold standard.",[15,139,140],{},[114,141,142],{},"Why Argon2?",[144,145,146,150,153],"ul",{},[147,148,149],"li",{},"Resistant to GPU-based attacks (memory-hard)",[147,151,152],{},"Configurable memory, time, and parallelism parameters",[147,154,155],{},"Available in most modern languages",[15,157,158,161],{},[114,159,160],{},"Implementation (Node.js)",":",[163,164,169],"pre",{"className":165,"code":166,"language":167,"meta":168,"style":168},"language-javascript shiki shiki-themes github-light github-dark","const argon2 = require('argon2');\n\nasync function hashPassword(password) {\n  return await argon2.hash(password, {\n    type: argon2.argon2id,  \u002F\u002F Hybrid version (recommended)\n    memoryCost: 65536,      \u002F\u002F 64 MB\n    timeCost: 3,            \u002F\u002F 3 iterations\n    parallelism: 4          \u002F\u002F 4 threads\n  });\n}\n\nasync function verifyPassword(hash, password) {\n  return await argon2.verify(hash, password);\n}\n","javascript","",[170,171,172,203,210,231,249,259,274,289,301,307,313,318,339,354],"code",{"__ignoreMap":168},[173,174,177,181,185,188,192,196,200],"span",{"class":175,"line":176},"line",1,[173,178,180],{"class":179},"szBVR","const",[173,182,184],{"class":183},"sj4cs"," argon2",[173,186,187],{"class":179}," =",[173,189,191],{"class":190},"sScJk"," require",[173,193,195],{"class":194},"sVt8B","(",[173,197,199],{"class":198},"sZZnC","'argon2'",[173,201,202],{"class":194},");\n",[173,204,206],{"class":175,"line":205},2,[173,207,209],{"emptyLinePlaceholder":208},true,"\n",[173,211,213,216,219,222,224,228],{"class":175,"line":212},3,[173,214,215],{"class":179},"async",[173,217,218],{"class":179}," function",[173,220,221],{"class":190}," hashPassword",[173,223,195],{"class":194},[173,225,227],{"class":226},"s4XuR","password",[173,229,230],{"class":194},") {\n",[173,232,234,237,240,243,246],{"class":175,"line":233},4,[173,235,236],{"class":179},"  return",[173,238,239],{"class":179}," await",[173,241,242],{"class":194}," argon2.",[173,244,245],{"class":190},"hash",[173,247,248],{"class":194},"(password, {\n",[173,250,252,255],{"class":175,"line":251},5,[173,253,254],{"class":194},"    type: argon2.argon2id,  ",[173,256,258],{"class":257},"sJ8bj","\u002F\u002F Hybrid version (recommended)\n",[173,260,262,265,268,271],{"class":175,"line":261},6,[173,263,264],{"class":194},"    memoryCost: ",[173,266,267],{"class":183},"65536",[173,269,270],{"class":194},",      ",[173,272,273],{"class":257},"\u002F\u002F 64 MB\n",[173,275,277,280,283,286],{"class":175,"line":276},7,[173,278,279],{"class":194},"    timeCost: ",[173,281,282],{"class":183},"3",[173,284,285],{"class":194},",            ",[173,287,288],{"class":257},"\u002F\u002F 3 iterations\n",[173,290,292,295,298],{"class":175,"line":291},8,[173,293,294],{"class":194},"    parallelism: ",[173,296,297],{"class":183},"4",[173,299,300],{"class":257},"          \u002F\u002F 4 threads\n",[173,302,304],{"class":175,"line":303},9,[173,305,306],{"class":194},"  });\n",[173,308,310],{"class":175,"line":309},10,[173,311,312],{"class":194},"}\n",[173,314,316],{"class":175,"line":315},11,[173,317,209],{"emptyLinePlaceholder":208},[173,319,321,323,325,328,330,332,335,337],{"class":175,"line":320},12,[173,322,215],{"class":179},[173,324,218],{"class":179},[173,326,327],{"class":190}," verifyPassword",[173,329,195],{"class":194},[173,331,245],{"class":226},[173,333,334],{"class":194},", ",[173,336,227],{"class":226},[173,338,230],{"class":194},[173,340,342,344,346,348,351],{"class":175,"line":341},13,[173,343,236],{"class":179},[173,345,239],{"class":179},[173,347,242],{"class":194},[173,349,350],{"class":190},"verify",[173,352,353],{"class":194},"(hash, password);\n",[173,355,357],{"class":175,"line":356},14,[173,358,312],{"class":194},[15,360,361,161],{},[114,362,363],{},"Implementation (Python)",[163,365,369],{"className":366,"code":367,"language":368,"meta":168,"style":168},"language-python shiki shiki-themes github-light github-dark","from argon2 import PasswordHasher\n\nph = PasswordHasher(\n    time_cost=3,\n    memory_cost=65536,\n    parallelism=4,\n    hash_len=32,\n    type=argon2.Type.ID\n)\n\nhash = ph.hash(\"user_password\")\nis_valid = ph.verify(hash, \"user_password\")\n","python",[170,370,371,376,380,385,390,395,400,405,410,415,419,424],{"__ignoreMap":168},[173,372,373],{"class":175,"line":176},[173,374,375],{},"from argon2 import PasswordHasher\n",[173,377,378],{"class":175,"line":205},[173,379,209],{"emptyLinePlaceholder":208},[173,381,382],{"class":175,"line":212},[173,383,384],{},"ph = PasswordHasher(\n",[173,386,387],{"class":175,"line":233},[173,388,389],{},"    time_cost=3,\n",[173,391,392],{"class":175,"line":251},[173,393,394],{},"    memory_cost=65536,\n",[173,396,397],{"class":175,"line":261},[173,398,399],{},"    parallelism=4,\n",[173,401,402],{"class":175,"line":276},[173,403,404],{},"    hash_len=32,\n",[173,406,407],{"class":175,"line":291},[173,408,409],{},"    type=argon2.Type.ID\n",[173,411,412],{"class":175,"line":303},[173,413,414],{},")\n",[173,416,417],{"class":175,"line":309},[173,418,209],{"emptyLinePlaceholder":208},[173,420,421],{"class":175,"line":315},[173,422,423],{},"hash = ph.hash(\"user_password\")\n",[173,425,426],{"class":175,"line":320},[173,427,428],{},"is_valid = ph.verify(hash, \"user_password\")\n",[131,430,432],{"id":431},"_2-bcrypt-battle-tested-alternative","2. bcrypt (Battle-Tested Alternative)",[15,434,435],{},"bcrypt has been the industry standard for over a decade and remains a solid choice.",[163,437,439],{"className":165,"code":438,"language":167,"meta":168,"style":168},"const bcrypt = require('bcrypt');\nconst saltRounds = 12;  \u002F\u002F Minimum 12 in 2026\n\nasync function hashPassword(password) {\n  return await bcrypt.hash(password, saltRounds);\n}\n\nasync function verifyPassword(hash, password) {\n  return await bcrypt.compare(password, hash);\n}\n",[170,440,441,459,477,481,495,509,513,517,535,549],{"__ignoreMap":168},[173,442,443,445,448,450,452,454,457],{"class":175,"line":176},[173,444,180],{"class":179},[173,446,447],{"class":183}," bcrypt",[173,449,187],{"class":179},[173,451,191],{"class":190},[173,453,195],{"class":194},[173,455,456],{"class":198},"'bcrypt'",[173,458,202],{"class":194},[173,460,461,463,466,468,471,474],{"class":175,"line":205},[173,462,180],{"class":179},[173,464,465],{"class":183}," saltRounds",[173,467,187],{"class":179},[173,469,470],{"class":183}," 12",[173,472,473],{"class":194},";  ",[173,475,476],{"class":257},"\u002F\u002F Minimum 12 in 2026\n",[173,478,479],{"class":175,"line":212},[173,480,209],{"emptyLinePlaceholder":208},[173,482,483,485,487,489,491,493],{"class":175,"line":233},[173,484,215],{"class":179},[173,486,218],{"class":179},[173,488,221],{"class":190},[173,490,195],{"class":194},[173,492,227],{"class":226},[173,494,230],{"class":194},[173,496,497,499,501,504,506],{"class":175,"line":251},[173,498,236],{"class":179},[173,500,239],{"class":179},[173,502,503],{"class":194}," bcrypt.",[173,505,245],{"class":190},[173,507,508],{"class":194},"(password, saltRounds);\n",[173,510,511],{"class":175,"line":261},[173,512,312],{"class":194},[173,514,515],{"class":175,"line":276},[173,516,209],{"emptyLinePlaceholder":208},[173,518,519,521,523,525,527,529,531,533],{"class":175,"line":291},[173,520,215],{"class":179},[173,522,218],{"class":179},[173,524,327],{"class":190},[173,526,195],{"class":194},[173,528,245],{"class":226},[173,530,334],{"class":194},[173,532,227],{"class":226},[173,534,230],{"class":194},[173,536,537,539,541,543,546],{"class":175,"line":303},[173,538,236],{"class":179},[173,540,239],{"class":179},[173,542,503],{"class":194},[173,544,545],{"class":190},"compare",[173,547,548],{"class":194},"(password, hash);\n",[173,550,551],{"class":175,"line":309},[173,552,312],{"class":194},[163,554,556],{"className":366,"code":555,"language":368,"meta":168,"style":168},"import bcrypt\n\npassword = b\"user_password\"\nhashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))\nis_valid = bcrypt.checkpw(password, hashed)\n",[170,557,558,563,567,572,577],{"__ignoreMap":168},[173,559,560],{"class":175,"line":176},[173,561,562],{},"import bcrypt\n",[173,564,565],{"class":175,"line":205},[173,566,209],{"emptyLinePlaceholder":208},[173,568,569],{"class":175,"line":212},[173,570,571],{},"password = b\"user_password\"\n",[173,573,574],{"class":175,"line":233},[173,575,576],{},"hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))\n",[173,578,579],{"class":175,"line":251},[173,580,581],{},"is_valid = bcrypt.checkpw(password, hashed)\n",[131,583,585],{"id":584},"_3-algorithms-to-avoid","3. Algorithms to Avoid",[29,587,588,598],{},[32,589,590],{},[35,591,592,595],{},[38,593,594],{},"Algorithm",[38,596,597],{},"Why Avoid",[48,599,600,610,620,630,640],{},[35,601,602,607],{},[53,603,604],{},[114,605,606],{},"MD5",[53,608,609],{},"Broken, fast, easily reversible",[35,611,612,617],{},[53,613,614],{},[114,615,616],{},"SHA-1",[53,618,619],{},"Broken, too fast for password hashing",[35,621,622,627],{},[53,623,624],{},[114,625,626],{},"SHA-256\u002F512",[53,628,629],{},"Designed for speed, vulnerable to GPU attacks",[35,631,632,637],{},[53,633,634],{},[114,635,636],{},"PBKDF2",[53,638,639],{},"Still acceptable but inferior to Argon2",[35,641,642,647],{},[53,643,644],{},[114,645,646],{},"scrypt",[53,648,649],{},"Good but Argon2 is better",[10,651,653],{"id":652},"salting-essential-not-optional","Salting: Essential, Not Optional",[15,655,656,657,660],{},"A ",[114,658,659],{},"salt"," is a random value added to a password before hashing. Every password must have its own unique salt.",[126,662,664],{"id":663},"why-salts-matter","Why Salts Matter",[15,666,667],{},"Without salts, identical passwords produce identical hashes. Attackers can:",[669,670,671,677,683],"ol",{},[147,672,673,676],{},[114,674,675],{},"Use rainbow tables"," — precomputed hash lookup tables",[147,678,679,682],{},[114,680,681],{},"Detect duplicate passwords"," — \"user123 and user456 have the same password\"",[147,684,685,688],{},[114,686,687],{},"Amortize attack cost"," — cracking one hash cracks all identical ones",[126,690,692],{"id":691},"proper-salting","Proper Salting",[15,694,695],{},"Modern libraries (Argon2, bcrypt) handle salting automatically — the salt is embedded in the output hash.",[163,697,699],{"className":165,"code":698,"language":167,"meta":168,"style":168},"\u002F\u002F Argon2 output includes salt, params, and hash\n\u002F\u002F Example: $argon2id$v=19$m=65536,t=3,p=4$saltBase64$hashBase64\n\nconst hash = await argon2.hash(password);\n\u002F\u002F Store this entire string — it contains everything needed to verify\n",[170,700,701,706,711,715,733],{"__ignoreMap":168},[173,702,703],{"class":175,"line":176},[173,704,705],{"class":257},"\u002F\u002F Argon2 output includes salt, params, and hash\n",[173,707,708],{"class":175,"line":205},[173,709,710],{"class":257},"\u002F\u002F Example: $argon2id$v=19$m=65536,t=3,p=4$saltBase64$hashBase64\n",[173,712,713],{"class":175,"line":212},[173,714,209],{"emptyLinePlaceholder":208},[173,716,717,719,722,724,726,728,730],{"class":175,"line":233},[173,718,180],{"class":179},[173,720,721],{"class":183}," hash",[173,723,187],{"class":179},[173,725,239],{"class":179},[173,727,242],{"class":194},[173,729,245],{"class":190},[173,731,732],{"class":194},"(password);\n",[173,734,735],{"class":175,"line":251},[173,736,737],{"class":257},"\u002F\u002F Store this entire string — it contains everything needed to verify\n",[126,739,741],{"id":740},"legacy-systems-without-built-in-salting","Legacy Systems Without Built-in Salting",[15,743,744],{},"If using a primitive hash function (not recommended), generate a random salt:",[163,746,748],{"className":165,"code":747,"language":167,"meta":168,"style":168},"const crypto = require('crypto');\n\nfunction hashWithSalt(password) {\n  const salt = crypto.randomBytes(16).toString('hex');\n  const hash = crypto\n    .createHash('sha256')\n    .update(salt + password)\n    .digest('hex');\n  return `${salt}:${hash}`;  \u002F\u002F Store both\n}\n",[170,749,750,768,772,786,820,831,846,862,875,897],{"__ignoreMap":168},[173,751,752,754,757,759,761,763,766],{"class":175,"line":176},[173,753,180],{"class":179},[173,755,756],{"class":183}," crypto",[173,758,187],{"class":179},[173,760,191],{"class":190},[173,762,195],{"class":194},[173,764,765],{"class":198},"'crypto'",[173,767,202],{"class":194},[173,769,770],{"class":175,"line":205},[173,771,209],{"emptyLinePlaceholder":208},[173,773,774,777,780,782,784],{"class":175,"line":212},[173,775,776],{"class":179},"function",[173,778,779],{"class":190}," hashWithSalt",[173,781,195],{"class":194},[173,783,227],{"class":226},[173,785,230],{"class":194},[173,787,788,791,794,796,799,802,804,807,810,813,815,818],{"class":175,"line":233},[173,789,790],{"class":179},"  const",[173,792,793],{"class":183}," salt",[173,795,187],{"class":179},[173,797,798],{"class":194}," crypto.",[173,800,801],{"class":190},"randomBytes",[173,803,195],{"class":194},[173,805,806],{"class":183},"16",[173,808,809],{"class":194},").",[173,811,812],{"class":190},"toString",[173,814,195],{"class":194},[173,816,817],{"class":198},"'hex'",[173,819,202],{"class":194},[173,821,822,824,826,828],{"class":175,"line":251},[173,823,790],{"class":179},[173,825,721],{"class":183},[173,827,187],{"class":179},[173,829,830],{"class":194}," crypto\n",[173,832,833,836,839,841,844],{"class":175,"line":261},[173,834,835],{"class":194},"    .",[173,837,838],{"class":190},"createHash",[173,840,195],{"class":194},[173,842,843],{"class":198},"'sha256'",[173,845,414],{"class":194},[173,847,848,850,853,856,859],{"class":175,"line":276},[173,849,835],{"class":194},[173,851,852],{"class":190},"update",[173,854,855],{"class":194},"(salt ",[173,857,858],{"class":179},"+",[173,860,861],{"class":194}," password)\n",[173,863,864,866,869,871,873],{"class":175,"line":291},[173,865,835],{"class":194},[173,867,868],{"class":190},"digest",[173,870,195],{"class":194},[173,872,817],{"class":198},[173,874,202],{"class":194},[173,876,877,879,882,884,887,889,892,894],{"class":175,"line":303},[173,878,236],{"class":179},[173,880,881],{"class":198}," `${",[173,883,659],{"class":194},[173,885,886],{"class":198},"}:${",[173,888,245],{"class":194},[173,890,891],{"class":198},"}`",[173,893,473],{"class":194},[173,895,896],{"class":257},"\u002F\u002F Store both\n",[173,898,899],{"class":175,"line":309},[173,900,312],{"class":194},[15,902,903,906],{},[114,904,905],{},"Note",": This is for illustration only. Use Argon2 or bcrypt instead.",[10,908,910],{"id":909},"password-complexity-requirements","Password Complexity Requirements",[126,912,914],{"id":913},"the-old-way-outdated","The Old Way (Outdated)",[144,916,917,920,923],{},[147,918,919],{},"Must contain uppercase, lowercase, numbers, symbols",[147,921,922],{},"Must change every 90 days",[147,924,925],{},"Minimum 8 characters",[126,927,929],{"id":928},"the-2026-way-nist-guidelines","The 2026 Way (NIST Guidelines)",[669,931,932,938,944,950,956],{},[147,933,934,937],{},[114,935,936],{},"Minimum 12 characters"," — length matters more than complexity",[147,939,940,943],{},[114,941,942],{},"Allow all character types"," — including spaces, emojis, Unicode",[147,945,946,949],{},[114,947,948],{},"Check against breach databases"," — reject passwords found in known breaches",[147,951,952,955],{},[114,953,954],{},"No arbitrary expiration"," — only force changes after a breach",[147,957,958,961],{},[114,959,960],{},"No composition rules"," — they make passwords harder to remember, not stronger",[126,963,965],{"id":964},"passphrases-the-better-alternative","Passphrases: The Better Alternative",[15,967,968],{},"Encourage users to create passphrases instead of passwords:",[163,970,975],{"className":971,"code":973,"language":974},[972],"language-text","Weak: P@ssw0rd!\nStrong passphrase: correct-horse-battery-staple\n","text",[170,976,973],{"__ignoreMap":168},[15,978,979],{},"Passphrases are:",[144,981,982,985,988],{},[147,983,984],{},"Easier to remember",[147,986,987],{},"Longer (more entropy)",[147,989,990],{},"Resistant to brute force",[10,992,994],{"id":993},"multi-factor-authentication-mfa","Multi-Factor Authentication (MFA)",[15,996,997],{},"Even the strongest password can be phished or leaked. MFA adds a critical second layer of security.",[126,999,1001],{"id":1000},"mfa-methods-ranked-by-security","MFA Methods Ranked by Security",[29,1003,1004,1020],{},[32,1005,1006],{},[35,1007,1008,1011,1014,1017],{},[38,1009,1010],{},"Method",[38,1012,1013],{},"Security",[38,1015,1016],{},"Usability",[38,1018,1019],{},"Recommendation",[48,1021,1022,1036,1049,1061,1074],{},[35,1023,1024,1027,1030,1033],{},[53,1025,1026],{},"Hardware keys (YubiKey)",[53,1028,1029],{},"⭐⭐⭐⭐⭐",[53,1031,1032],{},"⭐⭐⭐",[53,1034,1035],{},"Best for high-security",[35,1037,1038,1041,1044,1046],{},[53,1039,1040],{},"TOTP (Google Authenticator)",[53,1042,1043],{},"⭐⭐⭐⭐",[53,1045,1043],{},[53,1047,1048],{},"Great balance",[35,1050,1051,1054,1056,1058],{},[53,1052,1053],{},"Push notifications",[53,1055,1032],{},[53,1057,1029],{},[53,1059,1060],{},"Good for mobile apps",[35,1062,1063,1066,1069,1071],{},[53,1064,1065],{},"SMS",[53,1067,1068],{},"⭐⭐",[53,1070,1029],{},[53,1072,1073],{},"Avoid if possible",[35,1075,1076,1079,1082,1084],{},[53,1077,1078],{},"Email",[53,1080,1081],{},"⭐",[53,1083,1029],{},[53,1085,1086],{},"Last resort only",[126,1088,1090],{"id":1089},"implementing-totp","Implementing TOTP",[163,1092,1094],{"className":165,"code":1093,"language":167,"meta":168,"style":168},"const speakeasy = require('speakeasy');\n\n\u002F\u002F Generate secret for user\nconst secret = speakeasy.generateSecret({ length: 20 });\n\n\u002F\u002F Verify token from user's authenticator app\nconst verified = speakeasy.totp.verify({\n  secret: secret.base32,\n  encoding: 'base32',\n  token: userInputToken\n});\n",[170,1095,1096,1114,1118,1123,1147,1151,1156,1173,1178,1189,1194],{"__ignoreMap":168},[173,1097,1098,1100,1103,1105,1107,1109,1112],{"class":175,"line":176},[173,1099,180],{"class":179},[173,1101,1102],{"class":183}," speakeasy",[173,1104,187],{"class":179},[173,1106,191],{"class":190},[173,1108,195],{"class":194},[173,1110,1111],{"class":198},"'speakeasy'",[173,1113,202],{"class":194},[173,1115,1116],{"class":175,"line":205},[173,1117,209],{"emptyLinePlaceholder":208},[173,1119,1120],{"class":175,"line":212},[173,1121,1122],{"class":257},"\u002F\u002F Generate secret for user\n",[173,1124,1125,1127,1130,1132,1135,1138,1141,1144],{"class":175,"line":233},[173,1126,180],{"class":179},[173,1128,1129],{"class":183}," secret",[173,1131,187],{"class":179},[173,1133,1134],{"class":194}," speakeasy.",[173,1136,1137],{"class":190},"generateSecret",[173,1139,1140],{"class":194},"({ length: ",[173,1142,1143],{"class":183},"20",[173,1145,1146],{"class":194}," });\n",[173,1148,1149],{"class":175,"line":251},[173,1150,209],{"emptyLinePlaceholder":208},[173,1152,1153],{"class":175,"line":261},[173,1154,1155],{"class":257},"\u002F\u002F Verify token from user's authenticator app\n",[173,1157,1158,1160,1163,1165,1168,1170],{"class":175,"line":276},[173,1159,180],{"class":179},[173,1161,1162],{"class":183}," verified",[173,1164,187],{"class":179},[173,1166,1167],{"class":194}," speakeasy.totp.",[173,1169,350],{"class":190},[173,1171,1172],{"class":194},"({\n",[173,1174,1175],{"class":175,"line":291},[173,1176,1177],{"class":194},"  secret: secret.base32,\n",[173,1179,1180,1183,1186],{"class":175,"line":303},[173,1181,1182],{"class":194},"  encoding: ",[173,1184,1185],{"class":198},"'base32'",[173,1187,1188],{"class":194},",\n",[173,1190,1191],{"class":175,"line":309},[173,1192,1193],{"class":194},"  token: userInputToken\n",[173,1195,1196],{"class":175,"line":315},[173,1197,1198],{"class":194},"});\n",[10,1200,1202],{"id":1201},"password-managers","Password Managers",[15,1204,1205],{},"Password managers are the single most effective tool for improving password security. They allow users to:",[144,1207,1208,1211,1214,1217],{},[147,1209,1210],{},"Generate unique, random passwords for every site",[147,1212,1213],{},"Store passwords securely (encrypted locally)",[147,1215,1216],{},"Auto-fill login forms",[147,1218,1219],{},"Sync across devices",[126,1221,1223],{"id":1222},"popular-options","Popular Options",[29,1225,1226,1239],{},[32,1227,1228],{},[35,1229,1230,1233,1236],{},[38,1231,1232],{},"Manager",[38,1234,1235],{},"Type",[38,1237,1238],{},"Key Features",[48,1240,1241,1252,1263,1274],{},[35,1242,1243,1246,1249],{},[53,1244,1245],{},"Bitwarden",[53,1247,1248],{},"Cloud\u002Fself-hosted",[53,1250,1251],{},"Open source, free tier",[35,1253,1254,1257,1260],{},[53,1255,1256],{},"1Password",[53,1258,1259],{},"Cloud",[53,1261,1262],{},"Excellent UX, travel mode",[35,1264,1265,1268,1271],{},[53,1266,1267],{},"KeePassXC",[53,1269,1270],{},"Local",[53,1272,1273],{},"Offline, fully open source",[35,1275,1276,1279,1281],{},[53,1277,1278],{},"Dashlane",[53,1280,1259],{},[53,1282,1283],{},"VPN, dark web monitoring",[126,1285,1287],{"id":1286},"encourage-adoption","Encourage Adoption",[144,1289,1290,1293,1296],{},[147,1291,1292],{},"Educate users about password managers in your app",[147,1294,1295],{},"Consider offering a free trial or integration",[147,1297,1298,1299,1304],{},"Link to our ",[1300,1301,1303],"a",{"href":1302},"\u002Ftools\u002Fpassword-generator","Password Generator"," for generating strong passwords",[10,1306,1308],{"id":1307},"breach-detection-and-response","Breach Detection and Response",[126,1310,1312],{"id":1311},"have-i-been-pwned-integration","Have I Been Pwned Integration",[15,1314,1315],{},"Check user passwords against known breach databases:",[163,1317,1319],{"className":165,"code":1318,"language":167,"meta":168,"style":168},"const axios = require('axios');\n\nasync function checkBreach(password) {\n  const crypto = require('crypto');\n  const sha1 = crypto\n    .createHash('sha1')\n    .update(password)\n    .digest('hex')\n    .toUpperCase();\n\n  const prefix = sha1.substring(0, 5);\n  const suffix = sha1.substring(5);\n\n  const response = await axios.get(\n    `https:\u002F\u002Fapi.pwnedpasswords.com\u002Frange\u002F${prefix}`\n  );\n\n  return response.data.includes(suffix);\n}\n",[170,1320,1321,1339,1343,1358,1374,1385,1398,1407,1419,1429,1433,1460,1479,1483,1503,1515,1521,1526,1540],{"__ignoreMap":168},[173,1322,1323,1325,1328,1330,1332,1334,1337],{"class":175,"line":176},[173,1324,180],{"class":179},[173,1326,1327],{"class":183}," axios",[173,1329,187],{"class":179},[173,1331,191],{"class":190},[173,1333,195],{"class":194},[173,1335,1336],{"class":198},"'axios'",[173,1338,202],{"class":194},[173,1340,1341],{"class":175,"line":205},[173,1342,209],{"emptyLinePlaceholder":208},[173,1344,1345,1347,1349,1352,1354,1356],{"class":175,"line":212},[173,1346,215],{"class":179},[173,1348,218],{"class":179},[173,1350,1351],{"class":190}," checkBreach",[173,1353,195],{"class":194},[173,1355,227],{"class":226},[173,1357,230],{"class":194},[173,1359,1360,1362,1364,1366,1368,1370,1372],{"class":175,"line":233},[173,1361,790],{"class":179},[173,1363,756],{"class":183},[173,1365,187],{"class":179},[173,1367,191],{"class":190},[173,1369,195],{"class":194},[173,1371,765],{"class":198},[173,1373,202],{"class":194},[173,1375,1376,1378,1381,1383],{"class":175,"line":251},[173,1377,790],{"class":179},[173,1379,1380],{"class":183}," sha1",[173,1382,187],{"class":179},[173,1384,830],{"class":194},[173,1386,1387,1389,1391,1393,1396],{"class":175,"line":261},[173,1388,835],{"class":194},[173,1390,838],{"class":190},[173,1392,195],{"class":194},[173,1394,1395],{"class":198},"'sha1'",[173,1397,414],{"class":194},[173,1399,1400,1402,1404],{"class":175,"line":276},[173,1401,835],{"class":194},[173,1403,852],{"class":190},[173,1405,1406],{"class":194},"(password)\n",[173,1408,1409,1411,1413,1415,1417],{"class":175,"line":291},[173,1410,835],{"class":194},[173,1412,868],{"class":190},[173,1414,195],{"class":194},[173,1416,817],{"class":198},[173,1418,414],{"class":194},[173,1420,1421,1423,1426],{"class":175,"line":303},[173,1422,835],{"class":194},[173,1424,1425],{"class":190},"toUpperCase",[173,1427,1428],{"class":194},"();\n",[173,1430,1431],{"class":175,"line":309},[173,1432,209],{"emptyLinePlaceholder":208},[173,1434,1435,1437,1440,1442,1445,1448,1450,1453,1455,1458],{"class":175,"line":315},[173,1436,790],{"class":179},[173,1438,1439],{"class":183}," prefix",[173,1441,187],{"class":179},[173,1443,1444],{"class":194}," sha1.",[173,1446,1447],{"class":190},"substring",[173,1449,195],{"class":194},[173,1451,1452],{"class":183},"0",[173,1454,334],{"class":194},[173,1456,1457],{"class":183},"5",[173,1459,202],{"class":194},[173,1461,1462,1464,1467,1469,1471,1473,1475,1477],{"class":175,"line":320},[173,1463,790],{"class":179},[173,1465,1466],{"class":183}," suffix",[173,1468,187],{"class":179},[173,1470,1444],{"class":194},[173,1472,1447],{"class":190},[173,1474,195],{"class":194},[173,1476,1457],{"class":183},[173,1478,202],{"class":194},[173,1480,1481],{"class":175,"line":341},[173,1482,209],{"emptyLinePlaceholder":208},[173,1484,1485,1487,1490,1492,1494,1497,1500],{"class":175,"line":356},[173,1486,790],{"class":179},[173,1488,1489],{"class":183}," response",[173,1491,187],{"class":179},[173,1493,239],{"class":179},[173,1495,1496],{"class":194}," axios.",[173,1498,1499],{"class":190},"get",[173,1501,1502],{"class":194},"(\n",[173,1504,1506,1509,1512],{"class":175,"line":1505},15,[173,1507,1508],{"class":198},"    `https:\u002F\u002Fapi.pwnedpasswords.com\u002Frange\u002F${",[173,1510,1511],{"class":194},"prefix",[173,1513,1514],{"class":198},"}`\n",[173,1516,1518],{"class":175,"line":1517},16,[173,1519,1520],{"class":194},"  );\n",[173,1522,1524],{"class":175,"line":1523},17,[173,1525,209],{"emptyLinePlaceholder":208},[173,1527,1529,1531,1534,1537],{"class":175,"line":1528},18,[173,1530,236],{"class":179},[173,1532,1533],{"class":194}," response.data.",[173,1535,1536],{"class":190},"includes",[173,1538,1539],{"class":194},"(suffix);\n",[173,1541,1543],{"class":175,"line":1542},19,[173,1544,312],{"class":194},[15,1546,1547,1550],{},[114,1548,1549],{},"Important",": Use the k-anonymity approach (send only first 5 chars of hash) to protect user privacy.",[126,1552,1554],{"id":1553},"incident-response","Incident Response",[15,1556,1557],{},"When a breach occurs:",[669,1559,1560,1566,1572,1578,1584],{},[147,1561,1562,1565],{},[114,1563,1564],{},"Force password reset"," for affected accounts",[147,1567,1568,1571],{},[114,1569,1570],{},"Invalidate all sessions"," — log users out everywhere",[147,1573,1574,1577],{},[114,1575,1576],{},"Notify users"," — be transparent about what happened",[147,1579,1580,1583],{},[114,1581,1582],{},"Review logs"," — determine scope and entry point",[147,1585,1586,1589],{},[114,1587,1588],{},"Rotate secrets"," — API keys, encryption keys, etc.",[10,1591,1593],{"id":1592},"secure-password-reset-flows","Secure Password Reset Flows",[15,1595,1596],{},"A secure password reset flow:",[669,1598,1599,1605,1611,1614,1617,1620],{},[147,1600,1601,1602],{},"User requests reset → generate a ",[114,1603,1604],{},"time-limited, single-use token",[147,1606,1607,1608],{},"Send email with link → ",[170,1609,1610],{},"https:\u002F\u002Fexample.com\u002Freset?token=xyz",[147,1612,1613],{},"Validate token → check expiry and ensure it has not been used",[147,1615,1616],{},"Allow password set → enforce strength requirements",[147,1618,1619],{},"Invalidate token immediately after use",[147,1621,1622],{},"Notify user → send confirmation email",[126,1624,1626],{"id":1625},"what-not-to-do","What NOT to Do",[144,1628,1629,1632,1635,1638],{},[147,1630,1631],{},"❌ Email the new password to the user",[147,1633,1634],{},"❌ Use a permanent reset token",[147,1636,1637],{},"❌ Allow reset without confirming email",[147,1639,1640],{},"❌ Display \"user not found\" (leaks valid emails)",[10,1642,1644],{"id":1643},"organizational-policies","Organizational Policies",[126,1646,1648],{"id":1647},"for-developers","For Developers",[144,1650,1651,1654,1657,1660,1663],{},[147,1652,1653],{},"Use Argon2id with recommended parameters",[147,1655,1656],{},"Store only hashes, never plain text",[147,1658,1659],{},"Implement rate limiting on login (prevent brute force)",[147,1661,1662],{},"Log failed attempts and alert on anomalies",[147,1664,1665],{},"Use HTTPS everywhere (prevent credential interception)",[126,1667,1669],{"id":1668},"for-product-teams","For Product Teams",[144,1671,1672,1675,1678,1681,1684],{},[147,1673,1674],{},"Remove artificial password complexity rules",[147,1676,1677],{},"Allow password managers (don't block paste)",[147,1679,1680],{},"Add MFA enrollment during signup",[147,1682,1683],{},"Provide clear password requirement guidance",[147,1685,1686],{},"Offer a built-in password generator",[10,1688,1690],{"id":1689},"quick-security-checklist","Quick Security Checklist",[144,1692,1695,1704,1710,1716,1722,1728,1734,1740],{"className":1693},[1694],"contains-task-list",[147,1696,1699,1703],{"className":1697},[1698],"task-list-item",[1700,1701],"input",{"disabled":208,"type":1702},"checkbox"," Passwords are hashed with Argon2id or bcrypt (min 12 rounds)",[147,1705,1707,1709],{"className":1706},[1698],[1700,1708],{"disabled":208,"type":1702}," Each password has a unique, random salt",[147,1711,1713,1715],{"className":1712},[1698],[1700,1714],{"disabled":208,"type":1702}," MFA is available and encouraged",[147,1717,1719,1721],{"className":1718},[1698],[1700,1720],{"disabled":208,"type":1702}," Password reset tokens are single-use and time-limited",[147,1723,1725,1727],{"className":1724},[1698],[1700,1726],{"disabled":208,"type":1702}," Failed login attempts are rate-limited",[147,1729,1731,1733],{"className":1730},[1698],[1700,1732],{"disabled":208,"type":1702}," Passwords are checked against breach databases",[147,1735,1737,1739],{"className":1736},[1698],[1700,1738],{"disabled":208,"type":1702}," Minimum length is 12 characters (no arbitrary composition rules)",[147,1741,1743,1745],{"className":1742},[1698],[1700,1744],{"disabled":208,"type":1702}," Users are educated about password managers",[10,1747,1749],{"id":1748},"related-tools","Related Tools",[144,1751,1752],{},[147,1753,1754,1755],{},"Generate strong passwords with our ",[1300,1756,1303],{"href":1302},[1758,1759,1760],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":168,"searchDepth":205,"depth":205,"links":1762},[1763,1764,1765,1768,1773,1778,1782,1786,1790,1793,1797,1798],{"id":12,"depth":205,"text":13},{"id":23,"depth":205,"text":24},{"id":108,"depth":205,"text":109,"children":1766},[1767],{"id":128,"depth":212,"text":129},{"id":652,"depth":205,"text":653,"children":1769},[1770,1771,1772],{"id":663,"depth":212,"text":664},{"id":691,"depth":212,"text":692},{"id":740,"depth":212,"text":741},{"id":909,"depth":205,"text":910,"children":1774},[1775,1776,1777],{"id":913,"depth":212,"text":914},{"id":928,"depth":212,"text":929},{"id":964,"depth":212,"text":965},{"id":993,"depth":205,"text":994,"children":1779},[1780,1781],{"id":1000,"depth":212,"text":1001},{"id":1089,"depth":212,"text":1090},{"id":1201,"depth":205,"text":1202,"children":1783},[1784,1785],{"id":1222,"depth":212,"text":1223},{"id":1286,"depth":212,"text":1287},{"id":1307,"depth":205,"text":1308,"children":1787},[1788,1789],{"id":1311,"depth":212,"text":1312},{"id":1553,"depth":212,"text":1554},{"id":1592,"depth":205,"text":1593,"children":1791},[1792],{"id":1625,"depth":212,"text":1626},{"id":1643,"depth":205,"text":1644,"children":1794},[1795,1796],{"id":1647,"depth":212,"text":1648},{"id":1668,"depth":212,"text":1669},{"id":1689,"depth":205,"text":1690},{"id":1748,"depth":205,"text":1749},"2026-05-26","Learn password security best practices for 2026: secure hashing with Argon2\u002Fbcrypt, salting, MFA, password managers, and breach prevention.","md",{"immutable":208},"\u002Fguides\u002Fpassword-security",{"title":5,"description":1800},"guides\u002Fpassword-security","HcmtQ7LG4yzpwfRneLggNogke9iuCqCnB736CcCA-Xg",1780401324306]