[{"data":1,"prerenderedAt":20721},["ShallowReactive",2],{"/guides":3,"knowledgeBase":36},{"id":4,"title":5,"body":6,"date":27,"description":23,"extension":28,"head":27,"meta":29,"navigation":30,"ogImage":27,"path":31,"robots":27,"schemaOrg":27,"seo":32,"sitemap":33,"stem":34,"__hash__":35},"content/4.guides.md","Knowledge Base",{"type":7,"value":8,"toc":24},"minimark",[9],[10,11,12,19],"knowledge-base",{},[13,14,16],"template",{"v-slot:title":15},"",[17,18,5],"p",{},[13,20,21],{"v-slot:subtitle":15},[17,22,23],{},"Guides, tutorials, and my personal knowledge base & resources.",{"title":15,"searchDepth":25,"depth":25,"links":26},2,[],null,"md",{},true,"/guides",{"title":5,"description":23},{"loc":31},"4.guides","at3nKwwnFyYWGuOTSlAdQGf1dy3PCOe-oGHZ_JAgWEU",[37,1726,2389,6983,7937,8552,15564,16779,17670],{"id":38,"title":39,"body":40,"category":1710,"contentType":1711,"date":1712,"description":1713,"extension":28,"head":27,"image":1714,"meta":1715,"navigation":30,"ogImage":27,"path":1716,"readingTime":1717,"robots":27,"schemaOrg":27,"seo":1718,"sitemap":1719,"stem":1720,"tags":1721,"__hash__":1725},"knowledgeBase/knowledge-base/bash-aliases.md","Useful Bash Aliases",{"type":7,"value":41,"toc":1700},[42,45,57,62,65,94,97,114,117,134,145,278,282,285,326,329,346,349,366,369,386,389,406,410,413,430,433,450,453,470,477,693,697,700,781,784,820,823,868,872,875,890,893,910,913,929,932,948,951,966,969,986,997,1014,1017,1029,1032,1044,1047,1059,1062,1074,1077,1095,1098,1115,1118,1135,1139,1142,1159,1166,1183,1186,1203,1206,1223,1226,1243,1250,1267,1272,1289,1293,1296,1313,1316,1333,1336,1353,1356,1373,1376,1393,1396,1413,1416,1420,1430,1460,1463,1574,1577,1696],[17,43,44],{},"Bash aliases are shortcuts that map long or complex commands to shorter, memorable keywords. They save time, reduce typing errors, and significantly boost terminal productivity.",[17,46,47,48,52,53,56],{},"This is some of the aliases i use, categorized by their purpose. Add these to your ",[49,50,51],"code",{},"~/.bashrc"," or ",[49,54,55],{},"~/.bash_aliases"," file.",[58,59,61],"h3",{"id":60},"core-file-listing-and-color-support","Core File Listing and Color Support",[17,63,64],{},"List files in long format with file type indicators:",[66,67,72],"pre",{"className":68,"code":69,"filename":70,"language":71,"meta":15,"style":15},"language-bash shiki shiki-themes github-light github-dark github-dark","alias ll='ls -alF'\n","Terminal","bash",[49,73,74],{"__ignoreMap":15},[75,76,79,83,87,90],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"so5gQ","alias",[75,84,86],{"class":85},"slsVL"," ll",[75,88,89],{"class":81},"=",[75,91,93],{"class":92},"sfrk1","'ls -alF'\n",[17,95,96],{},"List all files including hidden ones:",[66,98,100],{"className":68,"code":99,"filename":70,"language":71,"meta":15,"style":15},"alias la='ls -A'\n",[49,101,102],{"__ignoreMap":15},[75,103,104,106,109,111],{"class":77,"line":78},[75,105,82],{"class":81},[75,107,108],{"class":85}," la",[75,110,89],{"class":81},[75,112,113],{"class":92},"'ls -A'\n",[17,115,116],{},"List files in columns:",[66,118,120],{"className":68,"code":119,"filename":70,"language":71,"meta":15,"style":15},"alias l='ls -CF'\n",[49,121,122],{"__ignoreMap":15},[75,123,124,126,129,131],{"class":77,"line":78},[75,125,82],{"class":81},[75,127,128],{"class":85}," l",[75,130,89],{"class":81},[75,132,133],{"class":92},"'ls -CF'\n",[17,135,136,137,140,141,144],{},"Enable automatic terminal color support for ",[49,138,139],{},"ls"," and ",[49,142,143],{},"grep"," commands:",[66,146,148],{"className":68,"code":147,"filename":70,"language":71,"meta":15,"style":15},"if [ -x /usr/bin/dircolors ]; then\n    test -r ~/.dircolors && eval \"$(dircolors -b ~/.dircolors)\" || eval \"$(dircolors -b)\"\n    \n    alias ls='ls --color=auto'\n    alias grep='grep --color=auto'\n    alias fgrep='fgrep --color=auto'\n    alias egrep='egrep --color=auto'\nfi\n",[49,149,150,167,213,219,233,246,259,272],{"__ignoreMap":15},[75,151,152,155,158,161,164],{"class":77,"line":78},[75,153,154],{"class":81},"if",[75,156,157],{"class":85}," [ ",[75,159,160],{"class":81},"-x",[75,162,163],{"class":85}," /usr/bin/dircolors ]; ",[75,165,166],{"class":81},"then\n",[75,168,169,173,176,179,182,185,188,192,195,198,201,204,206,208,210],{"class":77,"line":25},[75,170,172],{"class":171},"suiK_","    test",[75,174,175],{"class":171}," -r",[75,177,178],{"class":92}," ~/.dircolors",[75,180,181],{"class":85}," && ",[75,183,184],{"class":171},"eval",[75,186,187],{"class":92}," \"$(",[75,189,191],{"class":190},"shcOC","dircolors",[75,193,194],{"class":171}," -b",[75,196,197],{"class":92}," ~/.dircolors)\"",[75,199,200],{"class":81}," ||",[75,202,203],{"class":171}," eval",[75,205,187],{"class":92},[75,207,191],{"class":190},[75,209,194],{"class":171},[75,211,212],{"class":92},")\"\n",[75,214,216],{"class":77,"line":215},3,[75,217,218],{"class":85},"    \n",[75,220,222,225,228,230],{"class":77,"line":221},4,[75,223,224],{"class":81},"    alias",[75,226,227],{"class":85}," ls",[75,229,89],{"class":81},[75,231,232],{"class":92},"'ls --color=auto'\n",[75,234,236,238,241,243],{"class":77,"line":235},5,[75,237,224],{"class":81},[75,239,240],{"class":85}," grep",[75,242,89],{"class":81},[75,244,245],{"class":92},"'grep --color=auto'\n",[75,247,249,251,254,256],{"class":77,"line":248},6,[75,250,224],{"class":81},[75,252,253],{"class":85}," fgrep",[75,255,89],{"class":81},[75,257,258],{"class":92},"'fgrep --color=auto'\n",[75,260,262,264,267,269],{"class":77,"line":261},7,[75,263,224],{"class":81},[75,265,266],{"class":85}," egrep",[75,268,89],{"class":81},[75,270,271],{"class":92},"'egrep --color=auto'\n",[75,273,275],{"class":77,"line":274},8,[75,276,277],{"class":81},"fi\n",[58,279,281],{"id":280},"git-and-code-analysis","Git and Code Analysis",[17,283,284],{},"Stage and commit all modified files with a custom message:",[66,286,288],{"className":68,"code":287,"filename":70,"language":71,"meta":15,"style":15},"gac() {\n  git commit -a -m \"$1\"\n}\n",[49,289,290,298,321],{"__ignoreMap":15},[75,291,292,295],{"class":77,"line":78},[75,293,294],{"class":190},"gac",[75,296,297],{"class":85},"() {\n",[75,299,300,303,306,309,312,315,318],{"class":77,"line":25},[75,301,302],{"class":190},"  git",[75,304,305],{"class":92}," commit",[75,307,308],{"class":171}," -a",[75,310,311],{"class":171}," -m",[75,313,314],{"class":92}," \"",[75,316,317],{"class":171},"$1",[75,319,320],{"class":92},"\"\n",[75,322,323],{"class":77,"line":215},[75,324,325],{"class":85},"}\n",[17,327,328],{},"Count total lines of tracked code in the current Git repository:",[66,330,332],{"className":68,"code":331,"filename":70,"language":71,"meta":15,"style":15},"alias loc='git ls-files -z | xargs -0 cat | wc -l'\n",[49,333,334],{"__ignoreMap":15},[75,335,336,338,341,343],{"class":77,"line":78},[75,337,82],{"class":81},[75,339,340],{"class":85}," loc",[75,342,89],{"class":81},[75,344,345],{"class":92},"'git ls-files -z | xargs -0 cat | wc -l'\n",[17,347,348],{},"Count lines of code while ignoring lockfiles:",[66,350,352],{"className":68,"code":351,"filename":70,"language":71,"meta":15,"style":15},"alias loc_el=\"git ls-files | grep -vE 'package-lock\\.json|yarn\\.lock' | xargs -d '\\n' cat | wc -l\"\n",[49,353,354],{"__ignoreMap":15},[75,355,356,358,361,363],{"class":77,"line":78},[75,357,82],{"class":81},[75,359,360],{"class":85}," loc_el",[75,362,89],{"class":81},[75,364,365],{"class":92},"\"git ls-files | grep -vE 'package-lock\\.json|yarn\\.lock' | xargs -d '\\n' cat | wc -l\"\n",[17,367,368],{},"Count pure code lines, filtering out lockfiles, markdown, comments, and empty lines:",[66,370,372],{"className":68,"code":371,"filename":70,"language":71,"meta":15,"style":15},"alias eloc=\"git ls-files | grep -vE 'package-lock\\.json|yarn\\.lock|\\.md$' | xargs -d '\\n' cat | sed '/^\\s*\\/\\//d; /^\\s*\\/\\*/,/\\*\\//d; /^\\s*$/d' | wc -l\"\n",[49,373,374],{"__ignoreMap":15},[75,375,376,378,381,383],{"class":77,"line":78},[75,377,82],{"class":81},[75,379,380],{"class":85}," eloc",[75,382,89],{"class":81},[75,384,385],{"class":92},"\"git ls-files | grep -vE 'package-lock\\.json|yarn\\.lock|\\.md$' | xargs -d '\\n' cat | sed '/^\\s*\\/\\//d; /^\\s*\\/\\*/,/\\*\\//d; /^\\s*$/d' | wc -l\"\n",[17,387,388],{},"Display the exact timestamps of the first and last commits in the repository:",[66,390,392],{"className":68,"code":391,"filename":70,"language":71,"meta":15,"style":15},"alias ptimes='git log --reverse --format=\"%cd\" | head -n 1 && git log -1 --format=\"%cd\"'\n",[49,393,394],{"__ignoreMap":15},[75,395,396,398,401,403],{"class":77,"line":78},[75,397,82],{"class":81},[75,399,400],{"class":85}," ptimes",[75,402,89],{"class":81},[75,404,405],{"class":92},"'git log --reverse --format=\"%cd\" | head -n 1 && git log -1 --format=\"%cd\"'\n",[58,407,409],{"id":408},"networking-and-ports","Networking and Ports",[17,411,412],{},"List all active system processes listening on TCP ports:",[66,414,416],{"className":68,"code":415,"filename":70,"language":71,"meta":15,"style":15},"alias ls_tcp=\"sudo lsof -nP -iTCP -sTCP:LISTEN\"\n",[49,417,418],{"__ignoreMap":15},[75,419,420,422,425,427],{"class":77,"line":78},[75,421,82],{"class":81},[75,423,424],{"class":85}," ls_tcp",[75,426,89],{"class":81},[75,428,429],{"class":92},"\"sudo lsof -nP -iTCP -sTCP:LISTEN\"\n",[17,431,432],{},"List all active system processes listening on UDP ports:",[66,434,436],{"className":68,"code":435,"filename":70,"language":71,"meta":15,"style":15},"alias ls_udp=\"sudo lsof -nP -iUDP\"\n",[49,437,438],{"__ignoreMap":15},[75,439,440,442,445,447],{"class":77,"line":78},[75,441,82],{"class":81},[75,443,444],{"class":85}," ls_udp",[75,446,89],{"class":81},[75,448,449],{"class":92},"\"sudo lsof -nP -iUDP\"\n",[17,451,452],{},"Display all open listening network ports and routing sockets:",[66,454,456],{"className":68,"code":455,"filename":70,"language":71,"meta":15,"style":15},"alias lsp=\"sudo ss -tulpn\"\n",[49,457,458],{"__ignoreMap":15},[75,459,460,462,465,467],{"class":77,"line":78},[75,461,82],{"class":81},[75,463,464],{"class":85}," lsp",[75,466,89],{"class":81},[75,468,469],{"class":92},"\"sudo ss -tulpn\"\n",[17,471,472,473,476],{},"Find the exact process occupying a specific network port (Usage: ",[49,474,475],{},"listPort \u003Cport> [TCP|UDP]","):",[66,478,480],{"className":68,"code":479,"filename":70,"language":71,"meta":15,"style":15},"listPort() {\n  local port=$1\n  local proto=${2:-TCP}\n\n  if [ -n \"$port\" ]; then\n    proto=$(echo \"$proto\" | tr '[:lower:]' '[:upper:]')\n\n    if [ \"$proto\" = \"UDP\" ]; then\n      sudo lsof -nP -iUDP:\"$port\"\n    else\n      sudo lsof -nP -iTCP:\"$port\" -sTCP:LISTEN\n    fi\n  else\n    echo \"Usage: listPort \u003Cport_number> [TCP|UDP]\"\n  fi\n}\n",[49,481,482,489,503,523,528,551,586,590,613,634,640,661,667,673,682,688],{"__ignoreMap":15},[75,483,484,487],{"class":77,"line":78},[75,485,486],{"class":190},"listPort",[75,488,297],{"class":85},[75,490,491,494,497,499],{"class":77,"line":25},[75,492,493],{"class":81},"  local",[75,495,496],{"class":85}," port",[75,498,89],{"class":81},[75,500,502],{"class":501},"sQHwn","$1\n",[75,504,505,507,510,512,515,518,521],{"class":77,"line":215},[75,506,493],{"class":81},[75,508,509],{"class":85}," proto",[75,511,89],{"class":81},[75,513,514],{"class":501},"${2",[75,516,517],{"class":81},":-",[75,519,520],{"class":85},"TCP",[75,522,325],{"class":501},[75,524,525],{"class":77,"line":221},[75,526,527],{"emptyLinePlaceholder":30},"\n",[75,529,530,533,535,538,540,543,546,549],{"class":77,"line":235},[75,531,532],{"class":81},"  if",[75,534,157],{"class":85},[75,536,537],{"class":81},"-n",[75,539,314],{"class":92},[75,541,542],{"class":85},"$port",[75,544,545],{"class":92},"\"",[75,547,548],{"class":85}," ]; ",[75,550,166],{"class":81},[75,552,553,556,558,561,564,566,569,571,574,577,580,583],{"class":77,"line":248},[75,554,555],{"class":85},"    proto",[75,557,89],{"class":81},[75,559,560],{"class":85},"$(",[75,562,563],{"class":171},"echo",[75,565,314],{"class":92},[75,567,568],{"class":85},"$proto",[75,570,545],{"class":92},[75,572,573],{"class":81}," |",[75,575,576],{"class":190}," tr",[75,578,579],{"class":92}," '[:lower:]'",[75,581,582],{"class":92}," '[:upper:]'",[75,584,585],{"class":85},")\n",[75,587,588],{"class":77,"line":261},[75,589,527],{"emptyLinePlaceholder":30},[75,591,592,595,597,599,601,603,606,609,611],{"class":77,"line":274},[75,593,594],{"class":81},"    if",[75,596,157],{"class":85},[75,598,545],{"class":92},[75,600,568],{"class":85},[75,602,545],{"class":92},[75,604,605],{"class":81}," =",[75,607,608],{"class":92}," \"UDP\"",[75,610,548],{"class":85},[75,612,166],{"class":81},[75,614,616,619,622,625,628,630,632],{"class":77,"line":615},9,[75,617,618],{"class":190},"      sudo",[75,620,621],{"class":92}," lsof",[75,623,624],{"class":171}," -nP",[75,626,627],{"class":171}," -iUDP:",[75,629,545],{"class":92},[75,631,542],{"class":85},[75,633,320],{"class":92},[75,635,637],{"class":77,"line":636},10,[75,638,639],{"class":81},"    else\n",[75,641,643,645,647,649,652,654,656,658],{"class":77,"line":642},11,[75,644,618],{"class":190},[75,646,621],{"class":92},[75,648,624],{"class":171},[75,650,651],{"class":171}," -iTCP:",[75,653,545],{"class":92},[75,655,542],{"class":85},[75,657,545],{"class":92},[75,659,660],{"class":171}," -sTCP:LISTEN\n",[75,662,664],{"class":77,"line":663},12,[75,665,666],{"class":81},"    fi\n",[75,668,670],{"class":77,"line":669},13,[75,671,672],{"class":81},"  else\n",[75,674,676,679],{"class":77,"line":675},14,[75,677,678],{"class":171},"    echo",[75,680,681],{"class":92}," \"Usage: listPort \u003Cport_number> [TCP|UDP]\"\n",[75,683,685],{"class":77,"line":684},15,[75,686,687],{"class":81},"  fi\n",[75,689,691],{"class":77,"line":690},16,[75,692,325],{"class":85},[58,694,696],{"id":695},"miscellaneous-utilities","Miscellaneous Utilities",[17,698,699],{},"Stream an active log file starting exactly from the current moment:",[66,701,703],{"className":68,"code":702,"filename":70,"language":71,"meta":15,"style":15},"followlog() {\n  if [ -z \"$1\" ]; then\n    echo \"Error: No file path specified.\"\n    echo \"Usage: followlog /path/to/file\"\n    return 1\n  fi\n  tail -n 0 -f \"$1\"\n}\n",[49,704,705,712,731,738,745,753,757,777],{"__ignoreMap":15},[75,706,707,710],{"class":77,"line":78},[75,708,709],{"class":190},"followlog",[75,711,297],{"class":85},[75,713,714,716,718,721,723,725,727,729],{"class":77,"line":25},[75,715,532],{"class":81},[75,717,157],{"class":85},[75,719,720],{"class":81},"-z",[75,722,314],{"class":92},[75,724,317],{"class":171},[75,726,545],{"class":92},[75,728,548],{"class":85},[75,730,166],{"class":81},[75,732,733,735],{"class":77,"line":215},[75,734,678],{"class":171},[75,736,737],{"class":92}," \"Error: No file path specified.\"\n",[75,739,740,742],{"class":77,"line":221},[75,741,678],{"class":171},[75,743,744],{"class":92}," \"Usage: followlog /path/to/file\"\n",[75,746,747,750],{"class":77,"line":235},[75,748,749],{"class":81},"    return",[75,751,752],{"class":171}," 1\n",[75,754,755],{"class":77,"line":248},[75,756,687],{"class":81},[75,758,759,762,765,768,771,773,775],{"class":77,"line":261},[75,760,761],{"class":190},"  tail",[75,763,764],{"class":171}," -n",[75,766,767],{"class":171}," 0",[75,769,770],{"class":171}," -f",[75,772,314],{"class":92},[75,774,317],{"class":171},[75,776,320],{"class":92},[75,778,779],{"class":77,"line":274},[75,780,325],{"class":85},[17,782,783],{},"Fetch raw URL data and format it cleanly as JSON:",[66,785,787],{"className":68,"code":786,"filename":70,"language":71,"meta":15,"style":15},"jurl() {\n  curl \"$@\" | jq .\n}\n",[49,788,789,796,816],{"__ignoreMap":15},[75,790,791,794],{"class":77,"line":78},[75,792,793],{"class":190},"jurl",[75,795,297],{"class":85},[75,797,798,801,803,806,808,810,813],{"class":77,"line":25},[75,799,800],{"class":190},"  curl",[75,802,314],{"class":92},[75,804,805],{"class":171},"$@",[75,807,545],{"class":92},[75,809,573],{"class":81},[75,811,812],{"class":190}," jq",[75,814,815],{"class":92}," .\n",[75,817,818],{"class":77,"line":215},[75,819,325],{"class":85},[17,821,822],{},"Fetch and display a random dark or miscellaneous joke:",[66,824,826],{"className":68,"code":825,"filename":70,"language":71,"meta":15,"style":15},"joke() {\n    curl -s \"https://v2.jokeapi.dev/joke/Miscellaneous,Dark?blacklistFlags=nsfw,racist,sexist,explicit\" | \\\n    jq -r '.joke // (.setup + \"\\n\" + .delivery)'\n}\n",[49,827,828,835,851,864],{"__ignoreMap":15},[75,829,830,833],{"class":77,"line":78},[75,831,832],{"class":190},"joke",[75,834,297],{"class":85},[75,836,837,840,843,846,848],{"class":77,"line":25},[75,838,839],{"class":190},"    curl",[75,841,842],{"class":171}," -s",[75,844,845],{"class":92}," \"https://v2.jokeapi.dev/joke/Miscellaneous,Dark?blacklistFlags=nsfw,racist,sexist,explicit\"",[75,847,573],{"class":81},[75,849,850],{"class":171}," \\\n",[75,852,853,856,858,861],{"class":77,"line":215},[75,854,855],{"class":190},"    jq",[75,857,175],{"class":171},[75,859,860],{"class":92}," '.joke",[75,862,863],{"class":92}," // (.setup + \"\\n\" + .delivery)'\n",[75,865,866],{"class":77,"line":221},[75,867,325],{"class":85},[58,869,871],{"id":870},"directory-navigation","Directory Navigation",[17,873,874],{},"Enable color output for directory listing:",[66,876,878],{"className":68,"code":877,"filename":70,"language":71,"meta":15,"style":15},"alias ls='ls --color=auto'\n",[49,879,880],{"__ignoreMap":15},[75,881,882,884,886,888],{"class":77,"line":78},[75,883,82],{"class":81},[75,885,227],{"class":85},[75,887,89],{"class":81},[75,889,232],{"class":92},[17,891,892],{},"Correct common typo for listing files:",[66,894,896],{"className":68,"code":895,"filename":70,"language":71,"meta":15,"style":15},"alias sl='ls'\n",[49,897,898],{"__ignoreMap":15},[75,899,900,902,905,907],{"class":77,"line":78},[75,901,82],{"class":81},[75,903,904],{"class":85}," sl",[75,906,89],{"class":81},[75,908,909],{"class":92},"'ls'\n",[17,911,912],{},"List all files in a compact view, appending indicators for file types:",[66,914,916],{"className":68,"code":915,"filename":70,"language":71,"meta":15,"style":15},"alias la='ls -AF'\n",[49,917,918],{"__ignoreMap":15},[75,919,920,922,924,926],{"class":77,"line":78},[75,921,82],{"class":81},[75,923,108],{"class":85},[75,925,89],{"class":81},[75,927,928],{"class":92},"'ls -AF'\n",[17,930,931],{},"List all files in long format:",[66,933,935],{"className":68,"code":934,"filename":70,"language":71,"meta":15,"style":15},"alias ll='ls -Al'\n",[49,936,937],{"__ignoreMap":15},[75,938,939,941,943,945],{"class":77,"line":78},[75,940,82],{"class":81},[75,942,86],{"class":85},[75,944,89],{"class":81},[75,946,947],{"class":92},"'ls -Al'\n",[17,949,950],{},"List all files (including hidden ones):",[66,952,954],{"className":68,"code":953,"filename":70,"language":71,"meta":15,"style":15},"alias l='ls -A'\n",[49,955,956],{"__ignoreMap":15},[75,957,958,960,962,964],{"class":77,"line":78},[75,959,82],{"class":81},[75,961,128],{"class":85},[75,963,89],{"class":81},[75,965,113],{"class":92},[17,967,968],{},"List files one per line:",[66,970,972],{"className":68,"code":971,"filename":70,"language":71,"meta":15,"style":15},"alias l1='ls -1'\n",[49,973,974],{"__ignoreMap":15},[75,975,976,978,981,983],{"class":77,"line":78},[75,977,82],{"class":81},[75,979,980],{"class":85}," l1",[75,982,89],{"class":81},[75,984,985],{"class":92},"'ls -1'\n",[17,987,988,989,992,993,996],{},"List files with type indicators (e.g., ",[49,990,991],{},"/"," for directories, ",[49,994,995],{},"*"," for executables):",[66,998,1000],{"className":68,"code":999,"filename":70,"language":71,"meta":15,"style":15},"alias lf='ls -F'\n",[49,1001,1002],{"__ignoreMap":15},[75,1003,1004,1006,1009,1011],{"class":77,"line":78},[75,1005,82],{"class":81},[75,1007,1008],{"class":85}," lf",[75,1010,89],{"class":81},[75,1012,1013],{"class":92},"'ls -F'\n",[17,1015,1016],{},"Go up one directory:",[66,1018,1020],{"className":68,"code":1019,"filename":70,"language":71,"meta":15,"style":15},"alias ..='cd ..'\n",[49,1021,1022],{"__ignoreMap":15},[75,1023,1024,1026],{"class":77,"line":78},[75,1025,82],{"class":171},[75,1027,1028],{"class":92}," ..='cd ..'\n",[17,1030,1031],{},"Correct common typo for going up one directory:",[66,1033,1035],{"className":68,"code":1034,"filename":70,"language":71,"meta":15,"style":15},"alias cd..='cd ..'\n",[49,1036,1037],{"__ignoreMap":15},[75,1038,1039,1041],{"class":77,"line":78},[75,1040,82],{"class":171},[75,1042,1043],{"class":92}," cd..='cd ..'\n",[17,1045,1046],{},"Go up two directories:",[66,1048,1050],{"className":68,"code":1049,"filename":70,"language":71,"meta":15,"style":15},"alias ...='cd ../..'\n",[49,1051,1052],{"__ignoreMap":15},[75,1053,1054,1056],{"class":77,"line":78},[75,1055,82],{"class":171},[75,1057,1058],{"class":92}," ...='cd ../..'\n",[17,1060,1061],{},"Go up three directories:",[66,1063,1065],{"className":68,"code":1064,"filename":70,"language":71,"meta":15,"style":15},"alias ....='cd ../../..'\n",[49,1066,1067],{"__ignoreMap":15},[75,1068,1069,1071],{"class":77,"line":78},[75,1070,82],{"class":171},[75,1072,1073],{"class":92}," ....='cd ../../..'\n",[17,1075,1076],{},"Go back to the previous directory:",[66,1078,1080],{"className":68,"code":1079,"filename":70,"language":71,"meta":15,"style":15},"alias -- -='cd -'\n",[49,1081,1082],{"__ignoreMap":15},[75,1083,1084,1086,1089,1092],{"class":77,"line":78},[75,1085,82],{"class":171},[75,1087,1088],{"class":171}," --",[75,1090,1091],{"class":171}," -=",[75,1093,1094],{"class":92},"'cd -'\n",[17,1096,1097],{},"Create a directory and any necessary parent directories:",[66,1099,1101],{"className":68,"code":1100,"filename":70,"language":71,"meta":15,"style":15},"alias md='mkdir -p'\n",[49,1102,1103],{"__ignoreMap":15},[75,1104,1105,1107,1110,1112],{"class":77,"line":78},[75,1106,82],{"class":81},[75,1108,1109],{"class":85}," md",[75,1111,89],{"class":81},[75,1113,1114],{"class":92},"'mkdir -p'\n",[17,1116,1117],{},"Remove an empty directory:",[66,1119,1121],{"className":68,"code":1120,"filename":70,"language":71,"meta":15,"style":15},"alias rd='rmdir'\n",[49,1122,1123],{"__ignoreMap":15},[75,1124,1125,1127,1130,1132],{"class":77,"line":78},[75,1126,82],{"class":81},[75,1128,1129],{"class":85}," rd",[75,1131,89],{"class":81},[75,1133,1134],{"class":92},"'rmdir'\n",[58,1136,1138],{"id":1137},"editor-shortcuts","Editor Shortcuts",[17,1140,1141],{},"Open the default system editor (falls back to nano):",[66,1143,1145],{"className":68,"code":1144,"filename":70,"language":71,"meta":15,"style":15},"alias edit='${EDITOR:-${ALTERNATE_EDITOR:-nano}}'\n",[49,1146,1147],{"__ignoreMap":15},[75,1148,1149,1151,1154,1156],{"class":77,"line":78},[75,1150,82],{"class":81},[75,1152,1153],{"class":85}," edit",[75,1155,89],{"class":81},[75,1157,1158],{"class":92},"'${EDITOR:-${ALTERNATE_EDITOR:-nano}}'\n",[17,1160,1161,1162,1165],{},"Alias ",[49,1163,1164],{},"e"," to the default editor command:",[66,1167,1169],{"className":68,"code":1168,"filename":70,"language":71,"meta":15,"style":15},"alias e='edit'\n",[49,1170,1171],{"__ignoreMap":15},[75,1172,1173,1175,1178,1180],{"class":77,"line":78},[75,1174,82],{"class":81},[75,1176,1177],{"class":85}," e",[75,1179,89],{"class":81},[75,1181,1182],{"class":92},"'edit'\n",[17,1184,1185],{},"Open the default visual editor with root privileges (falls back to vim):",[66,1187,1189],{"className":68,"code":1188,"filename":70,"language":71,"meta":15,"style":15},"alias svim='sudo ${VISUAL:-vim}'\n",[49,1190,1191],{"__ignoreMap":15},[75,1192,1193,1195,1198,1200],{"class":77,"line":78},[75,1194,82],{"class":81},[75,1196,1197],{"class":85}," svim",[75,1199,89],{"class":81},[75,1201,1202],{"class":92},"'sudo ${VISUAL:-vim}'\n",[17,1204,1205],{},"Open nano with root privileges:",[66,1207,1209],{"className":68,"code":1208,"filename":70,"language":71,"meta":15,"style":15},"alias snano='sudo ${ALTERNATE_EDITOR:-nano}'\n",[49,1210,1211],{"__ignoreMap":15},[75,1212,1213,1215,1218,1220],{"class":77,"line":78},[75,1214,82],{"class":81},[75,1216,1217],{"class":85}," snano",[75,1219,89],{"class":81},[75,1221,1222],{"class":92},"'sudo ${ALTERNATE_EDITOR:-nano}'\n",[17,1224,1225],{},"Open the default system editor with root privileges:",[66,1227,1229],{"className":68,"code":1228,"filename":70,"language":71,"meta":15,"style":15},"alias sedit='sudo ${EDITOR:-${ALTERNATE_EDITOR:-nano}}'\n",[49,1230,1231],{"__ignoreMap":15},[75,1232,1233,1235,1238,1240],{"class":77,"line":78},[75,1234,82],{"class":81},[75,1236,1237],{"class":85}," sedit",[75,1239,89],{"class":81},[75,1241,1242],{"class":92},"'sudo ${EDITOR:-${ALTERNATE_EDITOR:-nano}}'\n",[17,1244,1245,1246,1249],{},"Quickly edit the ",[49,1247,1248],{},".bashrc"," configuration file:",[66,1251,1253],{"className":68,"code":1252,"filename":70,"language":71,"meta":15,"style":15},"alias vbrc='${VISUAL:-vim} ~/.bashrc'\n",[49,1254,1255],{"__ignoreMap":15},[75,1256,1257,1259,1262,1264],{"class":77,"line":78},[75,1258,82],{"class":81},[75,1260,1261],{"class":85}," vbrc",[75,1263,89],{"class":81},[75,1265,1266],{"class":92},"'${VISUAL:-vim} ~/.bashrc'\n",[17,1268,1245,1269,1249],{},[49,1270,1271],{},".bash_profile",[66,1273,1275],{"className":68,"code":1274,"filename":70,"language":71,"meta":15,"style":15},"alias vbpf='${VISUAL:-vim} ~/.bash_profile'\n",[49,1276,1277],{"__ignoreMap":15},[75,1278,1279,1281,1284,1286],{"class":77,"line":78},[75,1280,82],{"class":81},[75,1282,1283],{"class":85}," vbpf",[75,1285,89],{"class":81},[75,1287,1288],{"class":92},"'${VISUAL:-vim} ~/.bash_profile'\n",[58,1290,1292],{"id":1291},"general-utilities","General Utilities",[17,1294,1295],{},"Clear the terminal screen:",[66,1297,1299],{"className":68,"code":1298,"filename":70,"language":71,"meta":15,"style":15},"alias c='clear'\n",[49,1300,1301],{"__ignoreMap":15},[75,1302,1303,1305,1308,1310],{"class":77,"line":78},[75,1304,82],{"class":81},[75,1306,1307],{"class":85}," c",[75,1309,89],{"class":81},[75,1311,1312],{"class":92},"'clear'\n",[17,1314,1315],{},"Quickly exit the terminal session:",[66,1317,1319],{"className":68,"code":1318,"filename":70,"language":71,"meta":15,"style":15},"alias q='exit'\n",[49,1320,1321],{"__ignoreMap":15},[75,1322,1323,1325,1328,1330],{"class":77,"line":78},[75,1324,82],{"class":81},[75,1326,1327],{"class":85}," q",[75,1329,89],{"class":81},[75,1331,1332],{"class":92},"'exit'\n",[17,1334,1335],{},"Quickly navigate to the Downloads directory:",[66,1337,1339],{"className":68,"code":1338,"filename":70,"language":71,"meta":15,"style":15},"alias dow='cd $HOME/Downloads'\n",[49,1340,1341],{"__ignoreMap":15},[75,1342,1343,1345,1348,1350],{"class":77,"line":78},[75,1344,82],{"class":81},[75,1346,1347],{"class":85}," dow",[75,1349,89],{"class":81},[75,1351,1352],{"class":92},"'cd $HOME/Downloads'\n",[17,1354,1355],{},"Show the terminal command history:",[66,1357,1359],{"className":68,"code":1358,"filename":70,"language":71,"meta":15,"style":15},"alias h='history'\n",[49,1360,1361],{"__ignoreMap":15},[75,1362,1363,1365,1368,1370],{"class":77,"line":78},[75,1364,82],{"class":81},[75,1366,1367],{"class":85}," h",[75,1369,89],{"class":81},[75,1371,1372],{"class":92},"'history'\n",[17,1374,1375],{},"Display a graphical directory tree:",[66,1377,1379],{"className":68,"code":1378,"filename":70,"language":71,"meta":15,"style":15},"alias tree=\"find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'\"\n",[49,1380,1381],{"__ignoreMap":15},[75,1382,1383,1385,1388,1390],{"class":77,"line":78},[75,1384,82],{"class":81},[75,1386,1387],{"class":85}," tree",[75,1389,89],{"class":81},[75,1391,1392],{"class":92},"\"find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'\"\n",[17,1394,1395],{},"Recursively force remove files and directories:",[66,1397,1399],{"className":68,"code":1398,"filename":70,"language":71,"meta":15,"style":15},"alias rmrf='rm -rf'\n",[49,1400,1401],{"__ignoreMap":15},[75,1402,1403,1405,1408,1410],{"class":77,"line":78},[75,1404,82],{"class":81},[75,1406,1407],{"class":85}," rmrf",[75,1409,89],{"class":81},[75,1411,1412],{"class":92},"'rm -rf'\n",[1414,1415],"hr",{},[58,1417,1419],{"id":1418},"bash-it-commands","Bash-it Commands",[17,1421,1422,1429],{},[1423,1424,1428],"a",{"href":1425,"rel":1426},"https://github.com/Bash-it/bash-it",[1427],"nofollow","Bash-it"," is a community Bash framework. Install it and check out it's plugins, and aliases it provide.",[66,1431,1433],{"className":68,"code":1432,"language":71,"meta":15,"style":15},"$ bash-it show plugins\n$ bash-it show aliases\n",[49,1434,1435,1449],{"__ignoreMap":15},[75,1436,1437,1440,1443,1446],{"class":77,"line":78},[75,1438,1439],{"class":190},"$",[75,1441,1442],{"class":92}," bash-it",[75,1444,1445],{"class":92}," show",[75,1447,1448],{"class":92}," plugins\n",[75,1450,1451,1453,1455,1457],{"class":77,"line":25},[75,1452,1439],{"class":190},[75,1454,1442],{"class":92},[75,1456,1445],{"class":92},[75,1458,1459],{"class":92}," aliases\n",[17,1461,1462],{},"Some useful aliases to enable:",[1464,1465,1466,1480],"table",{},[1467,1468,1469],"thead",{},[1470,1471,1472,1477],"tr",{},[1473,1474,1476],"th",{"align":1475},"left","Alias",[1473,1478,1479],{"align":1475},"Description",[1481,1482,1483,1498,1508,1525,1535,1544,1554,1564],"tbody",{},[1470,1484,1485,1491],{},[1486,1487,1488],"td",{"align":1475},[49,1489,1490],{},"bash-it",[1486,1492,1493,1494,1497],{"align":1475},"Aliases for the bash-it command (automatically included with ",[49,1495,1496],{},"general",")",[1470,1499,1500,1505],{},[1486,1501,1502],{"align":1475},[49,1503,1504],{},"curl",[1486,1506,1507],{"align":1475},"Curl aliases for convenience.",[1470,1509,1510,1515],{},[1486,1511,1512],{"align":1475},[49,1513,1514],{},"directory",[1486,1516,1517,1518,1520,1521,1524],{"align":1475},"Shortcuts for directory commands (",[49,1519,139],{},", ",[49,1522,1523],{},"cd",", etc).",[1470,1526,1527,1532],{},[1486,1528,1529],{"align":1475},[49,1530,1531],{},"editor",[1486,1533,1534],{"align":1475},"Shortcuts for editing.",[1470,1536,1537,1541],{},[1486,1538,1539],{"align":1475},[49,1540,1496],{},[1486,1542,1543],{"align":1475},"General aliases.",[1470,1545,1546,1551],{},[1486,1547,1548],{"align":1475},[49,1549,1550],{},"git",[1486,1552,1553],{"align":1475},"Common git abbreviations.",[1470,1555,1556,1561],{},[1486,1557,1558],{"align":1475},[49,1559,1560],{},"node",[1486,1562,1563],{"align":1475},"The Node.js environment aliases.",[1470,1565,1566,1571],{},[1486,1567,1568],{"align":1475},[49,1569,1570],{},"npm",[1486,1572,1573],{"align":1475},"Common npm abbreviations.",[17,1575,1576],{},"Some useful plugins to enable:",[1464,1578,1579,1588],{},[1467,1580,1581],{},[1470,1582,1583,1586],{},[1473,1584,1585],{"align":1475},"Plugin",[1473,1587,1479],{"align":1475},[1481,1589,1590,1600,1617,1627,1637,1647,1657,1666,1676,1686],{},[1470,1591,1592,1597],{},[1486,1593,1594],{"align":1475},[49,1595,1596],{},"base",[1486,1598,1599],{"align":1475},"Miscellaneous tools",[1470,1601,1602,1607],{},[1486,1603,1604],{"align":1475},[49,1605,1606],{},"cht-sh",[1486,1608,1609,1610,1613,1614],{"align":1475},"Simplify ",[49,1611,1612],{},"curl cht.sh/\u003Cquery>"," to ",[49,1615,1616],{},"cht.sh \u003Cquery>",[1470,1618,1619,1624],{},[1486,1620,1621],{"align":1475},[49,1622,1623],{},"colors",[1486,1625,1626],{"align":1475},"Provides color definitions and ANSI functions for terminal output",[1470,1628,1629,1634],{},[1486,1630,1631],{"align":1475},[49,1632,1633],{},"docker-compose",[1486,1635,1636],{"align":1475},"Helper functions for using docker-compose",[1470,1638,1639,1644],{},[1486,1640,1641],{"align":1475},[49,1642,1643],{},"docker",[1486,1645,1646],{"align":1475},"Helpers to more easily work with Docker",[1470,1648,1649,1654],{},[1486,1650,1651],{"align":1475},[49,1652,1653],{},"explain",[1486,1655,1656],{"align":1475},"mankier.com explain function to explain other commands",[1470,1658,1659,1663],{},[1486,1660,1661],{"align":1475},[49,1662,1550],{},[1486,1664,1665],{"align":1475},"Git helper functions",[1470,1667,1668,1673],{},[1486,1669,1670],{"align":1475},[49,1671,1672],{},"history",[1486,1674,1675],{"align":1475},"Improve history handling with sane defaults",[1470,1677,1678,1683],{},[1486,1679,1680],{"align":1475},[49,1681,1682],{},"man",[1486,1684,1685],{"align":1475},"Colorize man pages for better readability",[1470,1687,1688,1693],{},[1486,1689,1690],{"align":1475},[49,1691,1692],{},"ssh",[1486,1694,1695],{"align":1475},"SSH helper functions",[1697,1698,1699],"style",{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":15,"searchDepth":25,"depth":25,"links":1701},[1702,1703,1704,1705,1706,1707,1708,1709],{"id":60,"depth":215,"text":61},{"id":280,"depth":215,"text":281},{"id":408,"depth":215,"text":409},{"id":695,"depth":215,"text":696},{"id":870,"depth":215,"text":871},{"id":1137,"depth":215,"text":1138},{"id":1291,"depth":215,"text":1292},{"id":1418,"depth":215,"text":1419},"terminal","cheatsheet","2026-07-15","A collection of useful Bash aliases to boost terminal productivity.","/knowledge-base/bash.png",{},"/knowledge-base/bash-aliases","5",{"title":39,"description":1713},{"loc":1716},"knowledge-base/bash-aliases",[1722,1723,70,1724],"Bash","Linux","Productivity","OGUP-brYKwdXWe7nFXMYRU1rB5XZcp_8gAI8l8aNwNc",{"id":1727,"title":1728,"body":1729,"category":2373,"contentType":2374,"date":1712,"description":2375,"extension":28,"head":27,"image":2376,"meta":2377,"navigation":30,"ogImage":27,"path":2378,"readingTime":2379,"robots":27,"schemaOrg":27,"seo":2380,"sitemap":2381,"stem":2382,"tags":2383,"__hash__":2388},"knowledgeBase/knowledge-base/cors.md","CORS Header Selection Cheatsheet",{"type":7,"value":1730,"toc":2363},[1731,1734,1736,1741,1803,1812,1814,1818,1851,1857,1859,1863,1868,1887,1902,1904,1908,1913,1928,1933,1935,1939,1965,1978,1980,1984,1992,2007,2031,2033,2037,2312,2314,2318,2357,2360],[17,1732,1733],{},"Use this guide to choose appropriate values for your CORS headers. Adjust settings to balance security and usability based on your API’s needs.",[1414,1735],{},[1737,1738,1740],"h2",{"id":1739},"_1-access-control-allow-origin","1. Access-Control-Allow-Origin",[1742,1743,1744,1763,1780],"ul",{},[1745,1746,1747,1751,1752,1497,1755],"li",{},[1748,1749,1750],"strong",{},"Specific Domain"," (e.g., ",[49,1753,1754],{},"https://example.com",[1742,1756,1757,1760],{},[1745,1758,1759],{},"Restricts access to only trusted sites.",[1745,1761,1762],{},"Use with credentials or sensitive data.",[1745,1764,1765,1768],{},[1748,1766,1767],{},"Dynamically Set Origin",[1742,1769,1770,1777],{},[1745,1771,1772,1773,1776],{},"Read the request’s ",[49,1774,1775],{},"Origin"," header and echo it back when it matches an allow-list.",[1745,1778,1779],{},"Prevents wildcard issues when using cookies or HTTP auth.",[1745,1781,1782,1787],{},[1748,1783,1784,1785,1497],{},"Wildcard (",[49,1786,995],{},[1742,1788,1789,1792],{},[1745,1790,1791],{},"Allows any site when credentials are NOT involved.",[1745,1793,1794,1798,1799,1802],{},[1795,1796,1797],"em",{},"Do not"," use with ",[49,1800,1801],{},"Access-Control-Allow-Credentials: true",".",[1804,1805,1806],"warning",{},[17,1807,1808,1809,1811],{},"Never combine ",[49,1810,995],{}," with credentials—browsers will ignore the wildcard and refuse the request.",[1414,1813],{},[1737,1815,1817],{"id":1816},"_2-access-control-allow-credentials","2. Access-Control-Allow-Credentials",[1742,1819,1820,1838],{},[1745,1821,1822,1827],{},[1748,1823,1824],{},[49,1825,1826],{},"true",[1742,1828,1829,1832],{},[1745,1830,1831],{},"Enable when your API must accept cookies, HTTP authentication, or client certificates.",[1745,1833,1834,1835,1802],{},"Must be paired with a non-wildcard ",[49,1836,1837],{},"Access-Control-Allow-Origin",[1745,1839,1840,1846],{},[1748,1841,1842,1843],{},"Omit or ",[49,1844,1845],{},"false",[1742,1847,1848],{},[1745,1849,1850],{},"Safer default if your API is completely public or stateless.",[1852,1853,1854],"tip",{},[17,1855,1856],{},"Omitting this header prevents browser-side credential leaks by default.",[1414,1858],{},[1737,1860,1862],{"id":1861},"_3-access-control-allow-methods","3. Access-Control-Allow-Methods",[1742,1864,1865],{},[1745,1866,1867],{},"List only the HTTP methods your API actually supports. For example:",[66,1869,1873],{"className":1870,"code":1871,"language":1872,"meta":15,"style":15},"language-http shiki shiki-themes github-light github-dark github-dark","Access-Control-Allow-Methods: GET, POST\n","http",[49,1874,1875],{"__ignoreMap":15},[75,1876,1877,1881,1884],{"class":77,"line":78},[75,1878,1880],{"class":1879},"sByVh","Access-Control-Allow-Methods",[75,1882,1883],{"class":81},":",[75,1885,1886],{"class":92}," GET, POST\n",[1742,1888,1889],{},[1745,1890,1891,1892,1520,1895,1520,1898,1901],{},"Avoid advertising methods you don’t implement (e.g., ",[49,1893,1894],{},"DELETE",[49,1896,1897],{},"PUT",[49,1899,1900],{},"PATCH",") to reduce attack surface.",[1414,1903],{},[1737,1905,1907],{"id":1906},"_4-access-control-allow-headers","4. Access-Control-Allow-Headers",[1742,1909,1910],{},[1745,1911,1912],{},"Specify only the headers your clients need to send. Common examples:",[66,1914,1916],{"className":1870,"code":1915,"language":1872,"meta":15,"style":15},"Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With\n",[49,1917,1918],{"__ignoreMap":15},[75,1919,1920,1923,1925],{"class":77,"line":78},[75,1921,1922],{"class":1879},"Access-Control-Allow-Headers",[75,1924,1883],{"class":81},[75,1926,1927],{"class":92}," Content-Type, Authorization, X-Requested-With\n",[1742,1929,1930],{},[1745,1931,1932],{},"Excluding unnecessary headers prevents unexpected or malicious data from being sent.",[1414,1934],{},[1737,1936,1938],{"id":1937},"_5-access-control-max-age","5. Access-Control-Max-Age",[1742,1940,1941,1944],{},[1745,1942,1943],{},"Controls how long the browser caches preflight responses (in seconds).",[1745,1945,1946,1949],{},[1748,1947,1948],{},"Performance vs. Security:",[1742,1950,1951,1958],{},[1745,1952,1953,1954,1957],{},"Longer cache (e.g., ",[49,1955,1956],{},"86400"," seconds = 24 hours) reduces preflight overhead.",[1745,1959,1960,1961,1964],{},"Shorter cache (e.g., ",[49,1962,1963],{},"600"," seconds) ensures policy changes take effect sooner.",[1966,1967,1968],"info",{},[17,1969,1970,1971,1974,1975,1977],{},"A common compromise is ",[49,1972,1973],{},"3600"," (1 hour) for dynamic APIs, or ",[49,1976,1956],{}," (24 hours) for stable endpoints.",[1414,1979],{},[1737,1981,1983],{"id":1982},"_6-access-control-expose-headers","6. Access-Control-Expose-Headers",[1742,1985,1986,1989],{},[1745,1987,1988],{},"Use when client-side JavaScript must read custom response headers beyond the CORS-safelisted ones.",[1745,1990,1991],{},"Example:",[66,1993,1995],{"className":1870,"code":1994,"language":1872,"meta":15,"style":15},"Access-Control-Expose-Headers: X-Total-Count, X-Request-ID\n",[49,1996,1997],{"__ignoreMap":15},[75,1998,1999,2002,2004],{"class":77,"line":78},[75,2000,2001],{"class":1879},"Access-Control-Expose-Headers",[75,2003,1883],{"class":81},[75,2005,2006],{"class":92}," X-Total-Count, X-Request-ID\n",[1742,2008,2009],{},[1745,2010,2011,2012,1520,2015,1520,2018,1520,2021,1520,2024,1520,2027,2030],{},"If omitted, browsers only expose simple headers (",[49,2013,2014],{},"Cache-Control",[49,2016,2017],{},"Content-Language",[49,2019,2020],{},"Content-Type",[49,2022,2023],{},"Expires",[49,2025,2026],{},"Last-Modified",[49,2028,2029],{},"Pragma",").",[1414,2032],{},[1737,2034,2036],{"id":2035},"example-configuration-snippet-expressjs","Example Configuration Snippet (Express.js)",[66,2038,2042],{"className":2039,"code":2040,"language":2041,"meta":15,"style":15},"language-js shiki shiki-themes github-light github-dark github-dark","app.use((req, res, next) => {\n  const allowedOrigins = ['https://app.example.com'];\n  const origin = req.headers.origin;\n\n  if (allowedOrigins.includes(origin)) {\n    res.header('Access-Control-Allow-Origin', origin);\n    res.header('Access-Control-Allow-Credentials', 'true');\n  }\n\n  res.header('Access-Control-Allow-Methods', 'GET,POST');\n  res.header(\n    'Access-Control-Allow-Headers',\n    'Content-Type, Authorization, X-Requested-With'\n  );\n  res.header('Access-Control-Max-Age', '3600');\n  res.header('Access-Control-Expose-Headers', 'X-Total-Count, X-Request-ID');\n\n  if (req.method === 'OPTIONS') {\n    return res.sendStatus(204);\n  }\n  next();\n});\n","js",[49,2043,2044,2077,2096,2108,2112,2125,2142,2161,2166,2170,2189,2198,2206,2211,2216,2234,2252,2257,2274,2292,2297,2306],{"__ignoreMap":15},[75,2045,2046,2049,2052,2055,2058,2060,2063,2065,2068,2071,2074],{"class":77,"line":78},[75,2047,2048],{"class":85},"app.",[75,2050,2051],{"class":190},"use",[75,2053,2054],{"class":85},"((",[75,2056,2057],{"class":501},"req",[75,2059,1520],{"class":85},[75,2061,2062],{"class":501},"res",[75,2064,1520],{"class":85},[75,2066,2067],{"class":501},"next",[75,2069,2070],{"class":85},") ",[75,2072,2073],{"class":81},"=>",[75,2075,2076],{"class":85}," {\n",[75,2078,2079,2082,2085,2087,2090,2093],{"class":77,"line":25},[75,2080,2081],{"class":81},"  const",[75,2083,2084],{"class":171}," allowedOrigins",[75,2086,605],{"class":81},[75,2088,2089],{"class":85}," [",[75,2091,2092],{"class":92},"'https://app.example.com'",[75,2094,2095],{"class":85},"];\n",[75,2097,2098,2100,2103,2105],{"class":77,"line":215},[75,2099,2081],{"class":81},[75,2101,2102],{"class":171}," origin",[75,2104,605],{"class":81},[75,2106,2107],{"class":85}," req.headers.origin;\n",[75,2109,2110],{"class":77,"line":221},[75,2111,527],{"emptyLinePlaceholder":30},[75,2113,2114,2116,2119,2122],{"class":77,"line":235},[75,2115,532],{"class":81},[75,2117,2118],{"class":85}," (allowedOrigins.",[75,2120,2121],{"class":190},"includes",[75,2123,2124],{"class":85},"(origin)) {\n",[75,2126,2127,2130,2133,2136,2139],{"class":77,"line":248},[75,2128,2129],{"class":85},"    res.",[75,2131,2132],{"class":190},"header",[75,2134,2135],{"class":85},"(",[75,2137,2138],{"class":92},"'Access-Control-Allow-Origin'",[75,2140,2141],{"class":85},", origin);\n",[75,2143,2144,2146,2148,2150,2153,2155,2158],{"class":77,"line":261},[75,2145,2129],{"class":85},[75,2147,2132],{"class":190},[75,2149,2135],{"class":85},[75,2151,2152],{"class":92},"'Access-Control-Allow-Credentials'",[75,2154,1520],{"class":85},[75,2156,2157],{"class":92},"'true'",[75,2159,2160],{"class":85},");\n",[75,2162,2163],{"class":77,"line":274},[75,2164,2165],{"class":85},"  }\n",[75,2167,2168],{"class":77,"line":615},[75,2169,527],{"emptyLinePlaceholder":30},[75,2171,2172,2175,2177,2179,2182,2184,2187],{"class":77,"line":636},[75,2173,2174],{"class":85},"  res.",[75,2176,2132],{"class":190},[75,2178,2135],{"class":85},[75,2180,2181],{"class":92},"'Access-Control-Allow-Methods'",[75,2183,1520],{"class":85},[75,2185,2186],{"class":92},"'GET,POST'",[75,2188,2160],{"class":85},[75,2190,2191,2193,2195],{"class":77,"line":642},[75,2192,2174],{"class":85},[75,2194,2132],{"class":190},[75,2196,2197],{"class":85},"(\n",[75,2199,2200,2203],{"class":77,"line":663},[75,2201,2202],{"class":92},"    'Access-Control-Allow-Headers'",[75,2204,2205],{"class":85},",\n",[75,2207,2208],{"class":77,"line":669},[75,2209,2210],{"class":92},"    'Content-Type, Authorization, X-Requested-With'\n",[75,2212,2213],{"class":77,"line":675},[75,2214,2215],{"class":85},"  );\n",[75,2217,2218,2220,2222,2224,2227,2229,2232],{"class":77,"line":684},[75,2219,2174],{"class":85},[75,2221,2132],{"class":190},[75,2223,2135],{"class":85},[75,2225,2226],{"class":92},"'Access-Control-Max-Age'",[75,2228,1520],{"class":85},[75,2230,2231],{"class":92},"'3600'",[75,2233,2160],{"class":85},[75,2235,2236,2238,2240,2242,2245,2247,2250],{"class":77,"line":690},[75,2237,2174],{"class":85},[75,2239,2132],{"class":190},[75,2241,2135],{"class":85},[75,2243,2244],{"class":92},"'Access-Control-Expose-Headers'",[75,2246,1520],{"class":85},[75,2248,2249],{"class":92},"'X-Total-Count, X-Request-ID'",[75,2251,2160],{"class":85},[75,2253,2255],{"class":77,"line":2254},17,[75,2256,527],{"emptyLinePlaceholder":30},[75,2258,2260,2262,2265,2268,2271],{"class":77,"line":2259},18,[75,2261,532],{"class":81},[75,2263,2264],{"class":85}," (req.method ",[75,2266,2267],{"class":81},"===",[75,2269,2270],{"class":92}," 'OPTIONS'",[75,2272,2273],{"class":85},") {\n",[75,2275,2277,2279,2282,2285,2287,2290],{"class":77,"line":2276},19,[75,2278,749],{"class":81},[75,2280,2281],{"class":85}," res.",[75,2283,2284],{"class":190},"sendStatus",[75,2286,2135],{"class":85},[75,2288,2289],{"class":171},"204",[75,2291,2160],{"class":85},[75,2293,2295],{"class":77,"line":2294},20,[75,2296,2165],{"class":85},[75,2298,2300,2303],{"class":77,"line":2299},21,[75,2301,2302],{"class":190},"  next",[75,2304,2305],{"class":85},"();\n",[75,2307,2309],{"class":77,"line":2308},22,[75,2310,2311],{"class":85},"});\n",[1414,2313],{},[1737,2315,2317],{"id":2316},"summary","Summary",[1742,2319,2320,2329,2335,2341,2351],{},[1745,2321,2322,2325,2326,2328],{},[1748,2323,2324],{},"Origin:"," Prefer specific domains or dynamic allow-lists over ",[49,2327,995],{}," when using credentials.",[1745,2330,2331,2334],{},[1748,2332,2333],{},"Credentials:"," Only enable if necessary and always pair with a non-wildcard origin.",[1745,2336,2337,2340],{},[1748,2338,2339],{},"Methods & Headers:"," Restrict to what your API supports and expects.",[1745,2342,2343,2346,2347,2350],{},[1748,2344,2345],{},"Caching:"," Tune ",[49,2348,2349],{},"Max-Age"," for your performance/security needs.",[1745,2352,2353,2356],{},[1748,2354,2355],{},"Expose:"," Declare any custom headers you need on the client side.",[17,2358,2359],{},"Keep this cheatsheet handy to ensure your CORS configuration remains secure and efficient!",[1697,2361,2362],{},"html pre.shiki code .sByVh, html code.shiki .sByVh{--shiki-light:#22863A;--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}",{"title":15,"searchDepth":25,"depth":25,"links":2364},[2365,2366,2367,2368,2369,2370,2371,2372],{"id":1739,"depth":25,"text":1740},{"id":1816,"depth":25,"text":1817},{"id":1861,"depth":25,"text":1862},{"id":1906,"depth":25,"text":1907},{"id":1937,"depth":25,"text":1938},{"id":1982,"depth":25,"text":1983},{"id":2035,"depth":25,"text":2036},{"id":2316,"depth":25,"text":2317},"networking","guide","A quick reference guide for configuring Cross-Origin Resource Sharing (CORS) headers.","/knowledge-base/cors.png",{},"/knowledge-base/cors","8",{"title":1728,"description":2375},{"loc":2378},"knowledge-base/cors",[2384,2385,2386,2387],"CORS","HTTP","Security","API","rzl1sWnBj8BTwIJneIQjSJt1UTywPmxPwpS-FQSGfMA",{"id":2390,"title":2391,"body":2392,"category":6969,"contentType":2374,"date":1712,"description":6970,"extension":28,"head":27,"image":6971,"meta":6972,"navigation":30,"ogImage":27,"path":6973,"readingTime":6974,"robots":27,"schemaOrg":27,"seo":6975,"sitemap":6976,"stem":6977,"tags":6978,"__hash__":6982},"knowledgeBase/knowledge-base/crypto.md","Key Concepts in crypto",{"type":7,"value":2393,"toc":6925},[2394,2401,2405,2411,2456,2460,2474,2481,2505,2510,2517,2544,2548,2571,2573,2577,2581,2584,2829,2834,2862,2866,2872,3164,3172,3204,3212,3232,3234,3241,3244,3263,3267,3476,3481,3513,3517,3522,3565,3569,3628,3632,3643,3687,3691,3959,3965,3970,3972,3979,3982,4129,4143,4145,4155,4161,4221,4226,4280,4288,4294,4338,4342,4379,4403,4405,4409,4417,4423,4428,4530,4544,4571,4577,4581,4682,4693,4698,4700,4704,4710,4719,4733,4737,4904,4913,4927,4933,4945,4950,5140,5149,5160,5277,5279,5283,5286,5309,5752,5779,5781,5785,5791,5822,5826,5912,5914,5918,6090,6092,6096,6288,6290,6294,6297,6300,6802,6805,6848,6851,6855,6904,6922],[1737,2395,2397,2398],{"id":2396},"_1-key-concepts-in-crypto","1. Key Concepts in ",[49,2399,2400],{},"crypto",[58,2402,2404],{"id":2403},"keyobject","KeyObject",[17,2406,2407,2408,2410],{},"Internally, Node represents public keys, private keys, and symmetric keys as ",[49,2409,2404],{}," instances.",[1742,2412,2413,2422],{},[1745,2414,2415,2416,2418,2419,2030],{},"A ",[49,2417,2404],{}," wraps the raw key material in a safe, memory-managed way (it’s not just a plain ",[49,2420,2421],{},"Buffer",[1745,2423,2424,2425,2427,2428],{},"You obtain a ",[49,2426,2404],{}," by:",[1742,2429,2430,2440,2450],{},[1745,2431,2432,2433,991,2436,2439],{},"Generating a new pair (e.g. ",[49,2434,2435],{},"generateKeyPair",[49,2437,2438],{},"generateKeyPairSync","),",[1745,2441,2442,2443,2446,2447,2439],{},"Importing existing PEM/DER/JWK data (via ",[49,2444,2445],{},"createPrivateKey"," / ",[49,2448,2449],{},"createPublicKey",[1745,2451,2452,2453,2030],{},"Exporting to PEM/DER/JWK (via ",[49,2454,2455],{},"keyObject.export()",[58,2457,2459],{"id":2458},"signing-verification","Signing & Verification",[1742,2461,2462,2468],{},[1745,2463,2464,2467],{},[1748,2465,2466],{},"Signing",": You prove “I (holder of the private key) authorize or attest to this exact message.”",[1745,2469,2470,2473],{},[1748,2471,2472],{},"Verification",": Anyone with the matching public key can check that signature→message mapping.",[17,2475,2476,2477,2480],{},"In Node, you use ",[49,2478,2479],{},"crypto.createSign(algorithm)"," to build a “Sign” instance, feed it data, then",[66,2482,2484],{"className":2039,"code":2483,"language":2041,"meta":15,"style":15},"const signature = sign.sign(privateKey);\n",[49,2485,2486],{"__ignoreMap":15},[75,2487,2488,2491,2494,2496,2499,2502],{"class":77,"line":78},[75,2489,2490],{"class":81},"const",[75,2492,2493],{"class":171}," signature",[75,2495,605],{"class":81},[75,2497,2498],{"class":85}," sign.",[75,2500,2501],{"class":190},"sign",[75,2503,2504],{"class":85},"(privateKey);\n",[17,2506,2507,2508,1802],{},"which returns a ",[49,2509,2421],{},[17,2511,2512,2513,2516],{},"To verify, you use ",[49,2514,2515],{},"crypto.createVerify(algorithm)",", feed it the same data, then:",[66,2518,2520],{"className":2039,"code":2519,"language":2041,"meta":15,"style":15},"const isValid = verify.verify(publicKey, signature); // boolean\n",[49,2521,2522],{"__ignoreMap":15},[75,2523,2524,2526,2529,2531,2534,2537,2540],{"class":77,"line":78},[75,2525,2490],{"class":81},[75,2527,2528],{"class":171}," isValid",[75,2530,605],{"class":81},[75,2532,2533],{"class":85}," verify.",[75,2535,2536],{"class":190},"verify",[75,2538,2539],{"class":85},"(publicKey, signature); ",[75,2541,2543],{"class":2542},"sCsY4","// boolean\n",[58,2545,2547],{"id":2546},"asymmetric-vs-symmetric","Asymmetric vs Symmetric",[1742,2549,2550,2556],{},[1745,2551,2552,2555],{},[1748,2553,2554],{},"Asymmetric algorithms"," (RSA, ECDSA, Ed25519, etc.) rely on a private/public key pair. The private key signs, the public key verifies.",[1745,2557,2558,2561,2562,2565,2566,52,2568,2570],{},[1748,2559,2560],{},"Symmetric algorithms"," (e.g. HMAC) use a shared secret key on both ends; Node handles HMAC via ",[49,2563,2564],{},"crypto.createHmac()"," and that key is just a ",[49,2567,2421],{},[49,2569,2404],{}," too.",[1414,2572],{},[1737,2574,2576],{"id":2575},"_2-creating-or-importing-keys","2. Creating or Importing Keys",[58,2578,2580],{"id":2579},"_21-generating-a-new-key-pair","2.1 Generating a New Key Pair",[17,2582,2583],{},"If you need to generate a fresh RSA or EC key pair for signing/verification:",[66,2585,2587],{"className":2039,"code":2586,"language":2041,"meta":15,"style":15},"import { generateKeyPairSync } from 'crypto';\n\n// Example: Generate an RSA key pair for signing (2048-bit, PKCS#1-style)\nconst { publicKey, privateKey } = generateKeyPairSync('rsa', {\n  modulusLength: 2048,\n  publicKeyEncoding: {\n    type: 'pkcs1',            // “pkcs1” or “spki” for public\n    format: 'pem'\n  },\n  privateKeyEncoding: {\n    type: 'pkcs1',            // “pkcs1” or “pkcs8” for private\n    format: 'pem',\n    cipher: 'aes-256-cbc',    // (optional) encrypt the PEM with a passphrase\n    passphrase: 'your-strong-passphrase'\n  }\n});\n\n// `publicKey` and `privateKey` here are PEM-encoded strings.\n// If you need KeyObject instances, you can immediately turn them into KeyObjects:\nimport { createPrivateKey, createPublicKey } from 'crypto';\nconst privKeyObject = createPrivateKey({\n  key: privateKey,\n  format: 'pem',\n  passphrase: 'your-strong-passphrase'\n});\nconst pubKeyObject = createPublicKey(publicKey);\n",[49,2588,2589,2606,2610,2615,2646,2656,2661,2675,2683,2688,2693,2704,2713,2727,2735,2739,2743,2747,2752,2757,2770,2785,2790,2800,2808,2813],{"__ignoreMap":15},[75,2590,2591,2594,2597,2600,2603],{"class":77,"line":78},[75,2592,2593],{"class":81},"import",[75,2595,2596],{"class":85}," { generateKeyPairSync } ",[75,2598,2599],{"class":81},"from",[75,2601,2602],{"class":92}," 'crypto'",[75,2604,2605],{"class":85},";\n",[75,2607,2608],{"class":77,"line":25},[75,2609,527],{"emptyLinePlaceholder":30},[75,2611,2612],{"class":77,"line":215},[75,2613,2614],{"class":2542},"// Example: Generate an RSA key pair for signing (2048-bit, PKCS#1-style)\n",[75,2616,2617,2619,2622,2625,2627,2630,2633,2635,2638,2640,2643],{"class":77,"line":221},[75,2618,2490],{"class":81},[75,2620,2621],{"class":85}," { ",[75,2623,2624],{"class":171},"publicKey",[75,2626,1520],{"class":85},[75,2628,2629],{"class":171},"privateKey",[75,2631,2632],{"class":85}," } ",[75,2634,89],{"class":81},[75,2636,2637],{"class":190}," generateKeyPairSync",[75,2639,2135],{"class":85},[75,2641,2642],{"class":92},"'rsa'",[75,2644,2645],{"class":85},", {\n",[75,2647,2648,2651,2654],{"class":77,"line":235},[75,2649,2650],{"class":85},"  modulusLength: ",[75,2652,2653],{"class":171},"2048",[75,2655,2205],{"class":85},[75,2657,2658],{"class":77,"line":248},[75,2659,2660],{"class":85},"  publicKeyEncoding: {\n",[75,2662,2663,2666,2669,2672],{"class":77,"line":261},[75,2664,2665],{"class":85},"    type: ",[75,2667,2668],{"class":92},"'pkcs1'",[75,2670,2671],{"class":85},",            ",[75,2673,2674],{"class":2542},"// “pkcs1” or “spki” for public\n",[75,2676,2677,2680],{"class":77,"line":274},[75,2678,2679],{"class":85},"    format: ",[75,2681,2682],{"class":92},"'pem'\n",[75,2684,2685],{"class":77,"line":615},[75,2686,2687],{"class":85},"  },\n",[75,2689,2690],{"class":77,"line":636},[75,2691,2692],{"class":85},"  privateKeyEncoding: {\n",[75,2694,2695,2697,2699,2701],{"class":77,"line":642},[75,2696,2665],{"class":85},[75,2698,2668],{"class":92},[75,2700,2671],{"class":85},[75,2702,2703],{"class":2542},"// “pkcs1” or “pkcs8” for private\n",[75,2705,2706,2708,2711],{"class":77,"line":663},[75,2707,2679],{"class":85},[75,2709,2710],{"class":92},"'pem'",[75,2712,2205],{"class":85},[75,2714,2715,2718,2721,2724],{"class":77,"line":669},[75,2716,2717],{"class":85},"    cipher: ",[75,2719,2720],{"class":92},"'aes-256-cbc'",[75,2722,2723],{"class":85},",    ",[75,2725,2726],{"class":2542},"// (optional) encrypt the PEM with a passphrase\n",[75,2728,2729,2732],{"class":77,"line":675},[75,2730,2731],{"class":85},"    passphrase: ",[75,2733,2734],{"class":92},"'your-strong-passphrase'\n",[75,2736,2737],{"class":77,"line":684},[75,2738,2165],{"class":85},[75,2740,2741],{"class":77,"line":690},[75,2742,2311],{"class":85},[75,2744,2745],{"class":77,"line":2254},[75,2746,527],{"emptyLinePlaceholder":30},[75,2748,2749],{"class":77,"line":2259},[75,2750,2751],{"class":2542},"// `publicKey` and `privateKey` here are PEM-encoded strings.\n",[75,2753,2754],{"class":77,"line":2276},[75,2755,2756],{"class":2542},"// If you need KeyObject instances, you can immediately turn them into KeyObjects:\n",[75,2758,2759,2761,2764,2766,2768],{"class":77,"line":2294},[75,2760,2593],{"class":81},[75,2762,2763],{"class":85}," { createPrivateKey, createPublicKey } ",[75,2765,2599],{"class":81},[75,2767,2602],{"class":92},[75,2769,2605],{"class":85},[75,2771,2772,2774,2777,2779,2782],{"class":77,"line":2299},[75,2773,2490],{"class":81},[75,2775,2776],{"class":171}," privKeyObject",[75,2778,605],{"class":81},[75,2780,2781],{"class":190}," createPrivateKey",[75,2783,2784],{"class":85},"({\n",[75,2786,2787],{"class":77,"line":2308},[75,2788,2789],{"class":85},"  key: privateKey,\n",[75,2791,2793,2796,2798],{"class":77,"line":2792},23,[75,2794,2795],{"class":85},"  format: ",[75,2797,2710],{"class":92},[75,2799,2205],{"class":85},[75,2801,2803,2806],{"class":77,"line":2802},24,[75,2804,2805],{"class":85},"  passphrase: ",[75,2807,2734],{"class":92},[75,2809,2811],{"class":77,"line":2810},25,[75,2812,2311],{"class":85},[75,2814,2816,2818,2821,2823,2826],{"class":77,"line":2815},26,[75,2817,2490],{"class":81},[75,2819,2820],{"class":171}," pubKeyObject",[75,2822,605],{"class":81},[75,2824,2825],{"class":190}," createPublicKey",[75,2827,2828],{"class":85},"(publicKey);\n",[17,2830,2831],{},[1748,2832,2833],{},"Best practice:",[1742,2835,2836,2852,2855],{},[1745,2837,2838,2839,1520,2842,2845,2846,52,2849,2030],{},"Use at least 2048-bit RSA or a 256-bit curve (e.g. ",[49,2840,2841],{},"rsa",[49,2843,2844],{},"ec"," with ",[49,2847,2848],{},"namedCurve: 'secp256k1'",[49,2850,2851],{},"'prime256v1'",[1745,2853,2854],{},"Protect your private key PEM with a strong passphrase if writing it to disk.",[1745,2856,2857,2858,2861],{},"Use ",[49,2859,2860],{},"generateKeyPairSync()"," only at startup or in build scripts—avoid calling it in a hot request/response path, since key generation can be CPU-intensive.",[58,2863,2865],{"id":2864},"_22-importing-an-existing-key-pemderjwk","2.2 Importing an Existing Key (PEM/DER/JWK)",[17,2867,2868,2869,2871],{},"Often you already have a PEM‐formatted key on disk (or in an environment variable). To use it in Node, wrap it in a ",[49,2870,2404],{}," via:",[66,2873,2875],{"className":2039,"code":2874,"language":2041,"meta":15,"style":15},"import { createPrivateKey, createPublicKey } from 'crypto';\n\n// 1. From a PEM string (e.g. loaded from process.env or fs.readFileSync):\nconst privPem = `-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6TAb... (base64) ...  \n-----END ENCRYPTED PRIVATE KEY-----`;\n\nconst privateKeyObject = createPrivateKey({\n  key: privPem,\n  format: 'pem',\n  passphrase: 'your-passphrase-if-encrypted'\n});\n\n// 2. From a DER buffer:\nimport fs from 'fs';\nconst derBuffer = fs.readFileSync('./myKey.der');\nconst publicKeyObject = createPublicKey({\n  key: derBuffer,\n  format: 'der',\n  type: 'spki'       // or \"pkcs1\" if your DER is in PKCS#1 format\n});\n\n// 3. From a JWK (JSON Web Key) object:\nconst jwk = {\n  kty: 'RSA',\n  n: '0vx7...bL8',\n  e: 'AQAB',\n  d: 'X4cT...DZu6__',\n  // plus p, q, dp, dq, qi if it's a private JWK\n};\nconst importedPrivateKey = createPrivateKey({ key: jwk, format: 'jwk' });\nconst importedPublicKey  = createPublicKey({ key: jwk, format: 'jwk' });\n",[49,2876,2877,2889,2893,2898,2910,2915,2922,2926,2939,2944,2952,2959,2963,2967,2972,2986,3008,3021,3026,3035,3046,3050,3054,3059,3070,3080,3090,3101,3112,3118,3124,3145],{"__ignoreMap":15},[75,2878,2879,2881,2883,2885,2887],{"class":77,"line":78},[75,2880,2593],{"class":81},[75,2882,2763],{"class":85},[75,2884,2599],{"class":81},[75,2886,2602],{"class":92},[75,2888,2605],{"class":85},[75,2890,2891],{"class":77,"line":25},[75,2892,527],{"emptyLinePlaceholder":30},[75,2894,2895],{"class":77,"line":215},[75,2896,2897],{"class":2542},"// 1. From a PEM string (e.g. loaded from process.env or fs.readFileSync):\n",[75,2899,2900,2902,2905,2907],{"class":77,"line":221},[75,2901,2490],{"class":81},[75,2903,2904],{"class":171}," privPem",[75,2906,605],{"class":81},[75,2908,2909],{"class":92}," `-----BEGIN ENCRYPTED PRIVATE KEY-----\n",[75,2911,2912],{"class":77,"line":235},[75,2913,2914],{"class":92},"MIIE6TAb... (base64) ...  \n",[75,2916,2917,2920],{"class":77,"line":248},[75,2918,2919],{"class":92},"-----END ENCRYPTED PRIVATE KEY-----`",[75,2921,2605],{"class":85},[75,2923,2924],{"class":77,"line":261},[75,2925,527],{"emptyLinePlaceholder":30},[75,2927,2928,2930,2933,2935,2937],{"class":77,"line":274},[75,2929,2490],{"class":81},[75,2931,2932],{"class":171}," privateKeyObject",[75,2934,605],{"class":81},[75,2936,2781],{"class":190},[75,2938,2784],{"class":85},[75,2940,2941],{"class":77,"line":615},[75,2942,2943],{"class":85},"  key: privPem,\n",[75,2945,2946,2948,2950],{"class":77,"line":636},[75,2947,2795],{"class":85},[75,2949,2710],{"class":92},[75,2951,2205],{"class":85},[75,2953,2954,2956],{"class":77,"line":642},[75,2955,2805],{"class":85},[75,2957,2958],{"class":92},"'your-passphrase-if-encrypted'\n",[75,2960,2961],{"class":77,"line":663},[75,2962,2311],{"class":85},[75,2964,2965],{"class":77,"line":669},[75,2966,527],{"emptyLinePlaceholder":30},[75,2968,2969],{"class":77,"line":675},[75,2970,2971],{"class":2542},"// 2. From a DER buffer:\n",[75,2973,2974,2976,2979,2981,2984],{"class":77,"line":684},[75,2975,2593],{"class":81},[75,2977,2978],{"class":85}," fs ",[75,2980,2599],{"class":81},[75,2982,2983],{"class":92}," 'fs'",[75,2985,2605],{"class":85},[75,2987,2988,2990,2993,2995,2998,3001,3003,3006],{"class":77,"line":690},[75,2989,2490],{"class":81},[75,2991,2992],{"class":171}," derBuffer",[75,2994,605],{"class":81},[75,2996,2997],{"class":85}," fs.",[75,2999,3000],{"class":190},"readFileSync",[75,3002,2135],{"class":85},[75,3004,3005],{"class":92},"'./myKey.der'",[75,3007,2160],{"class":85},[75,3009,3010,3012,3015,3017,3019],{"class":77,"line":2254},[75,3011,2490],{"class":81},[75,3013,3014],{"class":171}," publicKeyObject",[75,3016,605],{"class":81},[75,3018,2825],{"class":190},[75,3020,2784],{"class":85},[75,3022,3023],{"class":77,"line":2259},[75,3024,3025],{"class":85},"  key: derBuffer,\n",[75,3027,3028,3030,3033],{"class":77,"line":2276},[75,3029,2795],{"class":85},[75,3031,3032],{"class":92},"'der'",[75,3034,2205],{"class":85},[75,3036,3037,3040,3043],{"class":77,"line":2294},[75,3038,3039],{"class":85},"  type: ",[75,3041,3042],{"class":92},"'spki'",[75,3044,3045],{"class":2542},"       // or \"pkcs1\" if your DER is in PKCS#1 format\n",[75,3047,3048],{"class":77,"line":2299},[75,3049,2311],{"class":85},[75,3051,3052],{"class":77,"line":2308},[75,3053,527],{"emptyLinePlaceholder":30},[75,3055,3056],{"class":77,"line":2792},[75,3057,3058],{"class":2542},"// 3. From a JWK (JSON Web Key) object:\n",[75,3060,3061,3063,3066,3068],{"class":77,"line":2802},[75,3062,2490],{"class":81},[75,3064,3065],{"class":171}," jwk",[75,3067,605],{"class":81},[75,3069,2076],{"class":85},[75,3071,3072,3075,3078],{"class":77,"line":2810},[75,3073,3074],{"class":85},"  kty: ",[75,3076,3077],{"class":92},"'RSA'",[75,3079,2205],{"class":85},[75,3081,3082,3085,3088],{"class":77,"line":2815},[75,3083,3084],{"class":85},"  n: ",[75,3086,3087],{"class":92},"'0vx7...bL8'",[75,3089,2205],{"class":85},[75,3091,3093,3096,3099],{"class":77,"line":3092},27,[75,3094,3095],{"class":85},"  e: ",[75,3097,3098],{"class":92},"'AQAB'",[75,3100,2205],{"class":85},[75,3102,3104,3107,3110],{"class":77,"line":3103},28,[75,3105,3106],{"class":85},"  d: ",[75,3108,3109],{"class":92},"'X4cT...DZu6__'",[75,3111,2205],{"class":85},[75,3113,3115],{"class":77,"line":3114},29,[75,3116,3117],{"class":2542},"  // plus p, q, dp, dq, qi if it's a private JWK\n",[75,3119,3121],{"class":77,"line":3120},30,[75,3122,3123],{"class":85},"};\n",[75,3125,3127,3129,3132,3134,3136,3139,3142],{"class":77,"line":3126},31,[75,3128,2490],{"class":81},[75,3130,3131],{"class":171}," importedPrivateKey",[75,3133,605],{"class":81},[75,3135,2781],{"class":190},[75,3137,3138],{"class":85},"({ key: jwk, format: ",[75,3140,3141],{"class":92},"'jwk'",[75,3143,3144],{"class":85}," });\n",[75,3146,3148,3150,3153,3156,3158,3160,3162],{"class":77,"line":3147},32,[75,3149,2490],{"class":81},[75,3151,3152],{"class":171}," importedPublicKey",[75,3154,3155],{"class":81},"  =",[75,3157,2825],{"class":190},[75,3159,3138],{"class":85},[75,3161,3141],{"class":92},[75,3163,3144],{"class":85},[17,3165,3166],{},[1748,3167,3168,3169,1883],{},"Parameters for ",[49,3170,3171],{},"createPrivateKey()",[1742,3173,3174,3184,3192,3198],{},[1745,3175,3176,3179,3180,3183],{},[49,3177,3178],{},"key",": ",[49,3181,3182],{},"Buffer | string | object"," (e.g. JWK)",[1745,3185,3186,3179,3189],{},[49,3187,3188],{},"format",[49,3190,3191],{},"'pem' | 'der' | 'jwk'",[1745,3193,3194,3195,1802],{},"If the PEM is encrypted (PKCS#8 or PKCS#1), supply ",[49,3196,3197],{},"passphrase: string | Buffer",[1745,3199,3200,3201,1802],{},"If using DER, also specify ",[49,3202,3203],{},"type: 'pkcs1' | 'pkcs8'",[17,3205,3206,3211],{},[1748,3207,3168,3208],{},[49,3209,3210],{},"createPublicKey()"," are similar, but for public PEM/DER/JWK.",[17,3213,3214,3215,3217,3218,52,3221,3224,3225,3227,3228,3231],{},"Once you have a ",[49,3216,2404],{},", you can use it directly in ",[49,3219,3220],{},"crypto.sign(...)",[49,3222,3223],{},"createSign().sign(...)",". A ",[49,3226,2404],{}," is safer than passing around raw ",[49,3229,3230],{},"Buffers"," or strings.",[1414,3233],{},[1737,3235,3237,3238,1497],{"id":3236},"_3-signing-data-createsign","3. Signing Data (",[49,3239,3240],{},"createSign",[17,3242,3243],{},"When you want to produce a digital signature of some data (e.g. JSON payload, message, file hash), you typically:",[3245,3246,3247,3254,3257],"ol",{},[1745,3248,3249,3250,3253],{},"Hash the data (internally, ",[49,3251,3252],{},"Sign"," does it for you).",[1745,3255,3256],{},"Sign the hash with your private key.",[1745,3258,3259,3260,3262],{},"Return a signature ",[49,3261,2421],{}," (or base64/hex‐encoded string).",[58,3264,3266],{"id":3265},"_31-basic-flow","3.1 Basic Flow",[66,3268,3270],{"className":2039,"code":3269,"language":2041,"meta":15,"style":15},"import { createSign, constants } from 'crypto';\n\nconst data = Buffer.from('The quick brown fox'); // or a string\n\n// 1. Choose an algorithm. Common choices: 'RSA-SHA256', 'RSA-SHA512', 'SHA256', 'SHA512', 'ecdsa-with-SHA256', etc.\nconst sign = createSign('RSA-SHA256');\n\n// 2. Feed the data into the Sign object\nsign.update(data);\nsign.end(); // mark EOF\n\n// 3. Produce the signature. Pass either:\n//    • a `KeyObject` (recommended), OR\n//    • a PEM string (or deriving from it, e.g. passphrase if encrypted)\nconst signature = sign.sign({\n  key: privateKeyObject,   // e.g. from createPrivateKey() or generateKeyPairSync()\n  padding: constants.RSA_PKCS1_PSS_PADDING,    // If using RSA-PSS\n  saltLength: constants.RSA_PSS_SALTLEN_DIGEST // Recommended for PSS\n});\n// `signature` is a Buffer. You can `.toString('base64')` if needed for transport.\nconsole.log('Signature (base64):', signature.toString('base64'));\n",[49,3271,3272,3285,3289,3314,3318,3323,3342,3346,3351,3362,3375,3379,3384,3389,3394,3408,3416,3429,3440,3444,3449],{"__ignoreMap":15},[75,3273,3274,3276,3279,3281,3283],{"class":77,"line":78},[75,3275,2593],{"class":81},[75,3277,3278],{"class":85}," { createSign, constants } ",[75,3280,2599],{"class":81},[75,3282,2602],{"class":92},[75,3284,2605],{"class":85},[75,3286,3287],{"class":77,"line":25},[75,3288,527],{"emptyLinePlaceholder":30},[75,3290,3291,3293,3296,3298,3301,3303,3305,3308,3311],{"class":77,"line":215},[75,3292,2490],{"class":81},[75,3294,3295],{"class":171}," data",[75,3297,605],{"class":81},[75,3299,3300],{"class":85}," Buffer.",[75,3302,2599],{"class":190},[75,3304,2135],{"class":85},[75,3306,3307],{"class":92},"'The quick brown fox'",[75,3309,3310],{"class":85},"); ",[75,3312,3313],{"class":2542},"// or a string\n",[75,3315,3316],{"class":77,"line":221},[75,3317,527],{"emptyLinePlaceholder":30},[75,3319,3320],{"class":77,"line":235},[75,3321,3322],{"class":2542},"// 1. Choose an algorithm. Common choices: 'RSA-SHA256', 'RSA-SHA512', 'SHA256', 'SHA512', 'ecdsa-with-SHA256', etc.\n",[75,3324,3325,3327,3330,3332,3335,3337,3340],{"class":77,"line":248},[75,3326,2490],{"class":81},[75,3328,3329],{"class":171}," sign",[75,3331,605],{"class":81},[75,3333,3334],{"class":190}," createSign",[75,3336,2135],{"class":85},[75,3338,3339],{"class":92},"'RSA-SHA256'",[75,3341,2160],{"class":85},[75,3343,3344],{"class":77,"line":261},[75,3345,527],{"emptyLinePlaceholder":30},[75,3347,3348],{"class":77,"line":274},[75,3349,3350],{"class":2542},"// 2. Feed the data into the Sign object\n",[75,3352,3353,3356,3359],{"class":77,"line":615},[75,3354,3355],{"class":85},"sign.",[75,3357,3358],{"class":190},"update",[75,3360,3361],{"class":85},"(data);\n",[75,3363,3364,3366,3369,3372],{"class":77,"line":636},[75,3365,3355],{"class":85},[75,3367,3368],{"class":190},"end",[75,3370,3371],{"class":85},"(); ",[75,3373,3374],{"class":2542},"// mark EOF\n",[75,3376,3377],{"class":77,"line":642},[75,3378,527],{"emptyLinePlaceholder":30},[75,3380,3381],{"class":77,"line":663},[75,3382,3383],{"class":2542},"// 3. Produce the signature. Pass either:\n",[75,3385,3386],{"class":77,"line":669},[75,3387,3388],{"class":2542},"//    • a `KeyObject` (recommended), OR\n",[75,3390,3391],{"class":77,"line":675},[75,3392,3393],{"class":2542},"//    • a PEM string (or deriving from it, e.g. passphrase if encrypted)\n",[75,3395,3396,3398,3400,3402,3404,3406],{"class":77,"line":684},[75,3397,2490],{"class":81},[75,3399,2493],{"class":171},[75,3401,605],{"class":81},[75,3403,2498],{"class":85},[75,3405,2501],{"class":190},[75,3407,2784],{"class":85},[75,3409,3410,3413],{"class":77,"line":690},[75,3411,3412],{"class":85},"  key: privateKeyObject,   ",[75,3414,3415],{"class":2542},"// e.g. from createPrivateKey() or generateKeyPairSync()\n",[75,3417,3418,3421,3424,3426],{"class":77,"line":2254},[75,3419,3420],{"class":85},"  padding: constants.",[75,3422,3423],{"class":171},"RSA_PKCS1_PSS_PADDING",[75,3425,2723],{"class":85},[75,3427,3428],{"class":2542},"// If using RSA-PSS\n",[75,3430,3431,3434,3437],{"class":77,"line":2259},[75,3432,3433],{"class":85},"  saltLength: constants.",[75,3435,3436],{"class":171},"RSA_PSS_SALTLEN_DIGEST",[75,3438,3439],{"class":2542}," // Recommended for PSS\n",[75,3441,3442],{"class":77,"line":2276},[75,3443,2311],{"class":85},[75,3445,3446],{"class":77,"line":2294},[75,3447,3448],{"class":2542},"// `signature` is a Buffer. You can `.toString('base64')` if needed for transport.\n",[75,3450,3451,3454,3457,3459,3462,3465,3468,3470,3473],{"class":77,"line":2299},[75,3452,3453],{"class":85},"console.",[75,3455,3456],{"class":190},"log",[75,3458,2135],{"class":85},[75,3460,3461],{"class":92},"'Signature (base64):'",[75,3463,3464],{"class":85},", signature.",[75,3466,3467],{"class":190},"toString",[75,3469,2135],{"class":85},[75,3471,3472],{"class":92},"'base64'",[75,3474,3475],{"class":85},"));\n",[3477,3478,3480],"h4",{"id":3479},"algorithm-string","Algorithm string",[1742,3482,3483,3491,3506],{},[1745,3484,3485,3486,52,3488,1802],{},"For RSA with PKCS#1 v1.5 padding: use ",[49,3487,3339],{},[49,3489,3490],{},"'RSA-SHA512'",[1745,3492,3493,3494,3497,3498,3501,3502,3505],{},"For RSA-PSS (recommended for new designs), use ",[49,3495,3496],{},"createSign('sha256')"," (or ",[49,3499,3500],{},"'sha512'","), then in ",[49,3503,3504],{},"sign.sign({ padding: PSS_PADDING, saltLength: ... })"," you explicitly choose PSS.",[1745,3507,3508,3509,3512],{},"For ECDSA (e.g. P-256), use ",[49,3510,3511],{},"createSign('SHA256')",". Node outputs a DER‐encoded signature (r || s format) by default.",[3477,3514,3516],{"id":3515},"padding","Padding",[3518,3519,3521],"h5",{"id":3520},"for-rsapss","For RSA‐PSS:",[66,3523,3525],{"className":2039,"code":3524,"language":2041,"meta":15,"style":15},"const signature = sign.sign({\n  key: privateKeyObject,\n  padding: constants.RSA_PKCS1_PSS_PADDING,\n  saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n});\n",[49,3526,3527,3541,3546,3554,3561],{"__ignoreMap":15},[75,3528,3529,3531,3533,3535,3537,3539],{"class":77,"line":78},[75,3530,2490],{"class":81},[75,3532,2493],{"class":171},[75,3534,605],{"class":81},[75,3536,2498],{"class":85},[75,3538,2501],{"class":190},[75,3540,2784],{"class":85},[75,3542,3543],{"class":77,"line":25},[75,3544,3545],{"class":85},"  key: privateKeyObject,\n",[75,3547,3548,3550,3552],{"class":77,"line":215},[75,3549,3420],{"class":85},[75,3551,3423],{"class":171},[75,3553,2205],{"class":85},[75,3555,3556,3558],{"class":77,"line":221},[75,3557,3433],{"class":85},[75,3559,3560],{"class":171},"RSA_PSS_SALTLEN_DIGEST\n",[75,3562,3563],{"class":77,"line":235},[75,3564,2311],{"class":85},[3518,3566,3568],{"id":3567},"for-rsa-pkcs1-v15-legacy-but-still-common","For RSA PKCS#1 v1.5 (legacy, but still common):",[66,3570,3572],{"className":2039,"code":3571,"language":2041,"meta":15,"style":15},"// no need to specify padding (it defaults to PKCS#1 v1.5):\nconst signature = sign.sign(privateKeyObject);\n// or explicitly:\nconst signature = sign.sign({\n  key: privateKeyObject,\n  padding: constants.RSA_PKCS1_PADDING\n});\n",[49,3573,3574,3579,3594,3599,3613,3617,3624],{"__ignoreMap":15},[75,3575,3576],{"class":77,"line":78},[75,3577,3578],{"class":2542},"// no need to specify padding (it defaults to PKCS#1 v1.5):\n",[75,3580,3581,3583,3585,3587,3589,3591],{"class":77,"line":25},[75,3582,2490],{"class":81},[75,3584,2493],{"class":171},[75,3586,605],{"class":81},[75,3588,2498],{"class":85},[75,3590,2501],{"class":190},[75,3592,3593],{"class":85},"(privateKeyObject);\n",[75,3595,3596],{"class":77,"line":215},[75,3597,3598],{"class":2542},"// or explicitly:\n",[75,3600,3601,3603,3605,3607,3609,3611],{"class":77,"line":221},[75,3602,2490],{"class":81},[75,3604,2493],{"class":171},[75,3606,605],{"class":81},[75,3608,2498],{"class":85},[75,3610,2501],{"class":190},[75,3612,2784],{"class":85},[75,3614,3615],{"class":77,"line":235},[75,3616,3545],{"class":85},[75,3618,3619,3621],{"class":77,"line":248},[75,3620,3420],{"class":85},[75,3622,3623],{"class":171},"RSA_PKCS1_PADDING\n",[75,3625,3626],{"class":77,"line":261},[75,3627,2311],{"class":85},[3477,3629,3631],{"id":3630},"encoding","Encoding",[17,3633,3634,3635,3637,3638,52,3641,1883],{},"You can have Node return a ",[49,3636,2421],{},", or directly return a string in ",[49,3639,3640],{},"'hex'",[49,3642,3472],{},[66,3644,3646],{"className":2039,"code":3645,"language":2041,"meta":15,"style":15},"// returning as Base64 string (instead of Buffer)\nconst signatureBase64 = sign.sign({\n  key: privateKeyObject,\n  padding: constants.RSA_PKCS1_PADDING\n}, 'base64');\n",[49,3647,3648,3653,3668,3672,3678],{"__ignoreMap":15},[75,3649,3650],{"class":77,"line":78},[75,3651,3652],{"class":2542},"// returning as Base64 string (instead of Buffer)\n",[75,3654,3655,3657,3660,3662,3664,3666],{"class":77,"line":25},[75,3656,2490],{"class":81},[75,3658,3659],{"class":171}," signatureBase64",[75,3661,605],{"class":81},[75,3663,2498],{"class":85},[75,3665,2501],{"class":190},[75,3667,2784],{"class":85},[75,3669,3670],{"class":77,"line":215},[75,3671,3545],{"class":85},[75,3673,3674,3676],{"class":77,"line":221},[75,3675,3420],{"class":85},[75,3677,3623],{"class":171},[75,3679,3680,3683,3685],{"class":77,"line":235},[75,3681,3682],{"class":85},"}, ",[75,3684,3472],{"class":92},[75,3686,2160],{"class":85},[58,3688,3690],{"id":3689},"_32-example-with-ecdsa-elliptic-curve","3.2 Example with ECDSA (Elliptic Curve)",[66,3692,3694],{"className":2039,"code":3693,"language":2041,"meta":15,"style":15},"import { generateKeyPairSync, createSign, createPrivateKey, createPublicKey } from 'crypto';\n\n// 1. Generate an EC key pair (prime256v1 / secp256r1 is common)\nconst { publicKey: ecPubPem, privateKey: ecPrivPem } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n  publicKeyEncoding:  { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n});\n\nconst ecPrivKeyObject = createPrivateKey({ key: ecPrivPem, format: 'pem' });\nconst ecPubKeyObject = createPublicKey(ecPubPem);\n\n// 2. Sign:\nconst payload = Buffer.from(JSON.stringify({ foo: 'bar' }));\nconst sign = createSign('SHA256');\nsign.update(payload);\nsign.end();\n\n// ECDSA uses DER-encoded signature by default (r || s).\nconst signature = sign.sign(ecPrivKeyObject); // Buffer\n\nconsole.log('ECDSA Signature (hex):', signature.toString('hex'));\n",[49,3695,3696,3709,3713,3718,3753,3762,3777,3792,3796,3800,3818,3832,3836,3841,3873,3890,3899,3907,3911,3916,3934,3938],{"__ignoreMap":15},[75,3697,3698,3700,3703,3705,3707],{"class":77,"line":78},[75,3699,2593],{"class":81},[75,3701,3702],{"class":85}," { generateKeyPairSync, createSign, createPrivateKey, createPublicKey } ",[75,3704,2599],{"class":81},[75,3706,2602],{"class":92},[75,3708,2605],{"class":85},[75,3710,3711],{"class":77,"line":25},[75,3712,527],{"emptyLinePlaceholder":30},[75,3714,3715],{"class":77,"line":215},[75,3716,3717],{"class":2542},"// 1. Generate an EC key pair (prime256v1 / secp256r1 is common)\n",[75,3719,3720,3722,3724,3726,3728,3731,3733,3735,3737,3740,3742,3744,3746,3748,3751],{"class":77,"line":221},[75,3721,2490],{"class":81},[75,3723,2621],{"class":85},[75,3725,2624],{"class":501},[75,3727,3179],{"class":85},[75,3729,3730],{"class":171},"ecPubPem",[75,3732,1520],{"class":85},[75,3734,2629],{"class":501},[75,3736,3179],{"class":85},[75,3738,3739],{"class":171},"ecPrivPem",[75,3741,2632],{"class":85},[75,3743,89],{"class":81},[75,3745,2637],{"class":190},[75,3747,2135],{"class":85},[75,3749,3750],{"class":92},"'ec'",[75,3752,2645],{"class":85},[75,3754,3755,3758,3760],{"class":77,"line":235},[75,3756,3757],{"class":85},"  namedCurve: ",[75,3759,2851],{"class":92},[75,3761,2205],{"class":85},[75,3763,3764,3767,3769,3772,3774],{"class":77,"line":248},[75,3765,3766],{"class":85},"  publicKeyEncoding:  { type: ",[75,3768,3042],{"class":92},[75,3770,3771],{"class":85},", format: ",[75,3773,2710],{"class":92},[75,3775,3776],{"class":85}," },\n",[75,3778,3779,3782,3785,3787,3789],{"class":77,"line":261},[75,3780,3781],{"class":85},"  privateKeyEncoding: { type: ",[75,3783,3784],{"class":92},"'pkcs8'",[75,3786,3771],{"class":85},[75,3788,2710],{"class":92},[75,3790,3791],{"class":85}," }\n",[75,3793,3794],{"class":77,"line":274},[75,3795,2311],{"class":85},[75,3797,3798],{"class":77,"line":615},[75,3799,527],{"emptyLinePlaceholder":30},[75,3801,3802,3804,3807,3809,3811,3814,3816],{"class":77,"line":636},[75,3803,2490],{"class":81},[75,3805,3806],{"class":171}," ecPrivKeyObject",[75,3808,605],{"class":81},[75,3810,2781],{"class":190},[75,3812,3813],{"class":85},"({ key: ecPrivPem, format: ",[75,3815,2710],{"class":92},[75,3817,3144],{"class":85},[75,3819,3820,3822,3825,3827,3829],{"class":77,"line":642},[75,3821,2490],{"class":81},[75,3823,3824],{"class":171}," ecPubKeyObject",[75,3826,605],{"class":81},[75,3828,2825],{"class":190},[75,3830,3831],{"class":85},"(ecPubPem);\n",[75,3833,3834],{"class":77,"line":663},[75,3835,527],{"emptyLinePlaceholder":30},[75,3837,3838],{"class":77,"line":669},[75,3839,3840],{"class":2542},"// 2. Sign:\n",[75,3842,3843,3845,3848,3850,3852,3854,3856,3859,3861,3864,3867,3870],{"class":77,"line":675},[75,3844,2490],{"class":81},[75,3846,3847],{"class":171}," payload",[75,3849,605],{"class":81},[75,3851,3300],{"class":85},[75,3853,2599],{"class":190},[75,3855,2135],{"class":85},[75,3857,3858],{"class":171},"JSON",[75,3860,1802],{"class":85},[75,3862,3863],{"class":190},"stringify",[75,3865,3866],{"class":85},"({ foo: ",[75,3868,3869],{"class":92},"'bar'",[75,3871,3872],{"class":85}," }));\n",[75,3874,3875,3877,3879,3881,3883,3885,3888],{"class":77,"line":684},[75,3876,2490],{"class":81},[75,3878,3329],{"class":171},[75,3880,605],{"class":81},[75,3882,3334],{"class":190},[75,3884,2135],{"class":85},[75,3886,3887],{"class":92},"'SHA256'",[75,3889,2160],{"class":85},[75,3891,3892,3894,3896],{"class":77,"line":690},[75,3893,3355],{"class":85},[75,3895,3358],{"class":190},[75,3897,3898],{"class":85},"(payload);\n",[75,3900,3901,3903,3905],{"class":77,"line":2254},[75,3902,3355],{"class":85},[75,3904,3368],{"class":190},[75,3906,2305],{"class":85},[75,3908,3909],{"class":77,"line":2259},[75,3910,527],{"emptyLinePlaceholder":30},[75,3912,3913],{"class":77,"line":2276},[75,3914,3915],{"class":2542},"// ECDSA uses DER-encoded signature by default (r || s).\n",[75,3917,3918,3920,3922,3924,3926,3928,3931],{"class":77,"line":2294},[75,3919,2490],{"class":81},[75,3921,2493],{"class":171},[75,3923,605],{"class":81},[75,3925,2498],{"class":85},[75,3927,2501],{"class":190},[75,3929,3930],{"class":85},"(ecPrivKeyObject); ",[75,3932,3933],{"class":2542},"// Buffer\n",[75,3935,3936],{"class":77,"line":2299},[75,3937,527],{"emptyLinePlaceholder":30},[75,3939,3940,3942,3944,3946,3949,3951,3953,3955,3957],{"class":77,"line":2308},[75,3941,3453],{"class":85},[75,3943,3456],{"class":190},[75,3945,2135],{"class":85},[75,3947,3948],{"class":92},"'ECDSA Signature (hex):'",[75,3950,3464],{"class":85},[75,3952,3467],{"class":190},[75,3954,2135],{"class":85},[75,3956,3640],{"class":92},[75,3958,3475],{"class":85},[3960,3961,3962],"blockquote",{},[17,3963,3964],{},"For ECDSA, you do not specify padding: the default DER‐format output is correct.",[3960,3966,3967],{},[17,3968,3969],{},"Verification (below) will expect exactly that DER‐encoded signature.",[1414,3971],{},[1737,3973,3975,3976,1497],{"id":3974},"_4-verifying-a-signature-createverify","4. Verifying a Signature (",[49,3977,3978],{},"createVerify",[17,3980,3981],{},"Use the corresponding public key to verify a signature. You must feed the same data and algorithm:",[66,3983,3985],{"className":2039,"code":3984,"language":2041,"meta":15,"style":15},"import { createVerify, constants } from 'crypto';\n\n// Assume `payload`, `signature`, and `publicKeyObject` are known:\nconst verify = createVerify('RSA-SHA256');\nverify.update(payload);\nverify.end();\n\nconst isValid = verify.verify(\n  {\n    key: publicKeyObject, // the KeyObject or PEM string\n    padding: constants.RSA_PKCS1_PSS_PADDING,   // if you used PSS when signing\n    saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n  },\n  signature // Buffer, or if your signature was Base64, Buffer.from(sigBase64, 'base64')\n);\n\nconsole.log('Signature valid?', isValid);\n",[49,3986,3987,4000,4004,4009,4027,4036,4044,4048,4062,4067,4075,4088,4095,4099,4107,4111,4115],{"__ignoreMap":15},[75,3988,3989,3991,3994,3996,3998],{"class":77,"line":78},[75,3990,2593],{"class":81},[75,3992,3993],{"class":85}," { createVerify, constants } ",[75,3995,2599],{"class":81},[75,3997,2602],{"class":92},[75,3999,2605],{"class":85},[75,4001,4002],{"class":77,"line":25},[75,4003,527],{"emptyLinePlaceholder":30},[75,4005,4006],{"class":77,"line":215},[75,4007,4008],{"class":2542},"// Assume `payload`, `signature`, and `publicKeyObject` are known:\n",[75,4010,4011,4013,4016,4018,4021,4023,4025],{"class":77,"line":221},[75,4012,2490],{"class":81},[75,4014,4015],{"class":171}," verify",[75,4017,605],{"class":81},[75,4019,4020],{"class":190}," createVerify",[75,4022,2135],{"class":85},[75,4024,3339],{"class":92},[75,4026,2160],{"class":85},[75,4028,4029,4032,4034],{"class":77,"line":235},[75,4030,4031],{"class":85},"verify.",[75,4033,3358],{"class":190},[75,4035,3898],{"class":85},[75,4037,4038,4040,4042],{"class":77,"line":248},[75,4039,4031],{"class":85},[75,4041,3368],{"class":190},[75,4043,2305],{"class":85},[75,4045,4046],{"class":77,"line":261},[75,4047,527],{"emptyLinePlaceholder":30},[75,4049,4050,4052,4054,4056,4058,4060],{"class":77,"line":274},[75,4051,2490],{"class":81},[75,4053,2528],{"class":171},[75,4055,605],{"class":81},[75,4057,2533],{"class":85},[75,4059,2536],{"class":190},[75,4061,2197],{"class":85},[75,4063,4064],{"class":77,"line":615},[75,4065,4066],{"class":85},"  {\n",[75,4068,4069,4072],{"class":77,"line":636},[75,4070,4071],{"class":85},"    key: publicKeyObject, ",[75,4073,4074],{"class":2542},"// the KeyObject or PEM string\n",[75,4076,4077,4080,4082,4085],{"class":77,"line":642},[75,4078,4079],{"class":85},"    padding: constants.",[75,4081,3423],{"class":171},[75,4083,4084],{"class":85},",   ",[75,4086,4087],{"class":2542},"// if you used PSS when signing\n",[75,4089,4090,4093],{"class":77,"line":663},[75,4091,4092],{"class":85},"    saltLength: constants.",[75,4094,3560],{"class":171},[75,4096,4097],{"class":77,"line":669},[75,4098,2687],{"class":85},[75,4100,4101,4104],{"class":77,"line":675},[75,4102,4103],{"class":85},"  signature ",[75,4105,4106],{"class":2542},"// Buffer, or if your signature was Base64, Buffer.from(sigBase64, 'base64')\n",[75,4108,4109],{"class":77,"line":684},[75,4110,2160],{"class":85},[75,4112,4113],{"class":77,"line":690},[75,4114,527],{"emptyLinePlaceholder":30},[75,4116,4117,4119,4121,4123,4126],{"class":77,"line":2254},[75,4118,3453],{"class":85},[75,4120,3456],{"class":190},[75,4122,2135],{"class":85},[75,4124,4125],{"class":92},"'Signature valid?'",[75,4127,4128],{"class":85},", isValid);\n",[1742,4130,4131,4134],{},[1745,4132,4133],{},"Make sure the algorithm and padding match exactly what was used during signing.",[1745,4135,4136,4139,4140,4142],{},[49,4137,4138],{},"verify.verify(...)"," returns a boolean. If ",[49,4141,1845],{},", either data was altered or signature/public key mismatched.",[1414,4144],{},[1737,4146,4148,4149,4151,4152,4154],{"id":4147},"_5-createprivatekey-createpublickey-in-detail","5. ",[49,4150,2445],{}," & ",[49,4153,2449],{}," in Detail",[58,4156,4158],{"id":4157},"createprivatekeyoptions",[49,4159,4160],{},"createPrivateKey(options)",[1742,4162,4163,4212],{},[1745,4164,4165,4168],{},[1748,4166,4167],{},"Input:",[1742,4169,4170,4186,4193,4202],{},[1745,4171,4172,3179,4175,4178],{},[49,4173,4174],{},"options.key",[49,4176,4177],{},"string | Buffer | Object",[1742,4179,4180,4183],{},[1745,4181,4182],{},"If string/Buffer: PEM or DER.",[1745,4184,4185],{},"If object: Assume a JWK.",[1745,4187,4188,3179,4191,1802],{},[49,4189,4190],{},"options.format",[49,4192,3191],{},[1745,4194,4195,3179,4198,4201],{},[49,4196,4197],{},"options.type",[49,4199,4200],{},"'pkcs1' | 'pkcs8'"," (only for DER/PEM).",[1745,4203,4204,4207,4208,4211],{},[49,4205,4206],{},"options.passphrase",": (",[49,4209,4210],{},"string | Buffer",") if PEM is encrypted.",[1745,4213,4214,4217,4218,4220],{},[1748,4215,4216],{},"Output:"," A ",[49,4219,2404],{}," representing a private key.",[17,4222,4223],{},[1748,4224,4225],{},"Typical usage:",[66,4227,4229],{"className":2039,"code":4228,"language":2041,"meta":15,"style":15},"const pemString = fs.readFileSync('/secrets/key.pem', 'utf8');\nconst privObj = createPrivateKey({ key: pemString, format: 'pem', passphrase: '...' });\n",[49,4230,4231,4256],{"__ignoreMap":15},[75,4232,4233,4235,4238,4240,4242,4244,4246,4249,4251,4254],{"class":77,"line":78},[75,4234,2490],{"class":81},[75,4236,4237],{"class":171}," pemString",[75,4239,605],{"class":81},[75,4241,2997],{"class":85},[75,4243,3000],{"class":190},[75,4245,2135],{"class":85},[75,4247,4248],{"class":92},"'/secrets/key.pem'",[75,4250,1520],{"class":85},[75,4252,4253],{"class":92},"'utf8'",[75,4255,2160],{"class":85},[75,4257,4258,4260,4263,4265,4267,4270,4272,4275,4278],{"class":77,"line":25},[75,4259,2490],{"class":81},[75,4261,4262],{"class":171}," privObj",[75,4264,605],{"class":81},[75,4266,2781],{"class":190},[75,4268,4269],{"class":85},"({ key: pemString, format: ",[75,4271,2710],{"class":92},[75,4273,4274],{"class":85},", passphrase: ",[75,4276,4277],{"class":92},"'...'",[75,4279,3144],{"class":85},[1742,4281,4282],{},[1745,4283,4284,4285,4287],{},"Store ",[49,4286,2404],{}," in memory—Node will protect the key material from accidental exposure (e.g. not accidentally logged).",[58,4289,4291],{"id":4290},"createpublickeyoptions",[49,4292,4293],{},"createPublicKey(options)",[1742,4295,4296,4331],{},[1745,4297,4298,4300,4301,4304,4305],{},[1748,4299,4167],{}," Same as above (minus ",[49,4302,4303],{},"passphrase",", since public keys aren’t encrypted).",[1742,4306,4307,4316,4322],{},[1745,4308,4309,4311,4312,4315],{},[49,4310,3178],{},": public PEM (e.g. ",[49,4313,4314],{},"-----BEGIN PUBLIC KEY-----"," ...).",[1745,4317,4318,3179,4320,1802],{},[49,4319,3188],{},[49,4321,3191],{},[1745,4323,4324,3179,4327,4330],{},[49,4325,4326],{},"type",[49,4328,4329],{},"'spki' | 'pkcs1'"," (for public).",[1745,4332,4333,4217,4335,4337],{},[1748,4334,4216],{},[49,4336,2404],{}," for the public key.",[17,4339,4340],{},[1748,4341,4225],{},[66,4343,4345],{"className":2039,"code":4344,"language":2041,"meta":15,"style":15},"const pubPem = getFromAwsKms();\nconst pubObj = createPublicKey({ key: pubPem, format: 'pem' });\n",[49,4346,4347,4361],{"__ignoreMap":15},[75,4348,4349,4351,4354,4356,4359],{"class":77,"line":78},[75,4350,2490],{"class":81},[75,4352,4353],{"class":171}," pubPem",[75,4355,605],{"class":81},[75,4357,4358],{"class":190}," getFromAwsKms",[75,4360,2305],{"class":85},[75,4362,4363,4365,4368,4370,4372,4375,4377],{"class":77,"line":25},[75,4364,2490],{"class":81},[75,4366,4367],{"class":171}," pubObj",[75,4369,605],{"class":81},[75,4371,2825],{"class":190},[75,4373,4374],{"class":85},"({ key: pubPem, format: ",[75,4376,2710],{"class":92},[75,4378,3144],{"class":85},[1742,4380,4381],{},[1745,4382,3214,4383,4385,4386,4388,4389,1520,4392,4395,4396,991,4399,4402],{},[49,4384,2404],{},", you never need to deal with raw PEM/DER strings again—just pass the ",[49,4387,2404],{}," into ",[49,4390,4391],{},"sign()",[49,4393,4394],{},"verify()",", or even into ",[49,4397,4398],{},"encrypt()",[49,4400,4401],{},"decrypt()"," functions.",[1414,4404],{},[1737,4406,4408],{"id":4407},"_6-higher-level-signing-apis","6. Higher-Level Signing APIs",[17,4410,4411,4412,52,4414,4416],{},"Node also offers convenience methods so you don’t have to manage ",[49,4413,3240],{},[49,4415,3978],{}," objects manually:",[58,4418,4420],{"id":4419},"cryptosignalgorithm-data-privatekey",[49,4421,4422],{},"crypto.sign(algorithm, data, privateKey)",[17,4424,4425],{},[1748,4426,4427],{},"Usage:",[66,4429,4431],{"className":2039,"code":4430,"language":2041,"meta":15,"style":15},"import { sign, constants } from 'crypto';\n// data: Buffer or string\n// privateKey: KeyObject or PEM string or { key: pemString, passphrase, ... }\nconst signature = sign(\n  'RSA-SHA256',            // algorithm\n  Buffer.from('hello'),    // data\n  {\n    key: privateKeyObject,          // or PEM string + passphrase\n    padding: constants.RSA_PKCS1_PSS_PADDING,\n    saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n  }\n);\n",[49,4432,4433,4446,4451,4456,4468,4478,4496,4500,4508,4516,4522,4526],{"__ignoreMap":15},[75,4434,4435,4437,4440,4442,4444],{"class":77,"line":78},[75,4436,2593],{"class":81},[75,4438,4439],{"class":85}," { sign, constants } ",[75,4441,2599],{"class":81},[75,4443,2602],{"class":92},[75,4445,2605],{"class":85},[75,4447,4448],{"class":77,"line":25},[75,4449,4450],{"class":2542},"// data: Buffer or string\n",[75,4452,4453],{"class":77,"line":215},[75,4454,4455],{"class":2542},"// privateKey: KeyObject or PEM string or { key: pemString, passphrase, ... }\n",[75,4457,4458,4460,4462,4464,4466],{"class":77,"line":221},[75,4459,2490],{"class":81},[75,4461,2493],{"class":171},[75,4463,605],{"class":81},[75,4465,3329],{"class":190},[75,4467,2197],{"class":85},[75,4469,4470,4473,4475],{"class":77,"line":235},[75,4471,4472],{"class":92},"  'RSA-SHA256'",[75,4474,2671],{"class":85},[75,4476,4477],{"class":2542},"// algorithm\n",[75,4479,4480,4483,4485,4487,4490,4493],{"class":77,"line":248},[75,4481,4482],{"class":85},"  Buffer.",[75,4484,2599],{"class":190},[75,4486,2135],{"class":85},[75,4488,4489],{"class":92},"'hello'",[75,4491,4492],{"class":85},"),    ",[75,4494,4495],{"class":2542},"// data\n",[75,4497,4498],{"class":77,"line":261},[75,4499,4066],{"class":85},[75,4501,4502,4505],{"class":77,"line":274},[75,4503,4504],{"class":85},"    key: privateKeyObject,          ",[75,4506,4507],{"class":2542},"// or PEM string + passphrase\n",[75,4509,4510,4512,4514],{"class":77,"line":615},[75,4511,4079],{"class":85},[75,4513,3423],{"class":171},[75,4515,2205],{"class":85},[75,4517,4518,4520],{"class":77,"line":636},[75,4519,4092],{"class":85},[75,4521,3560],{"class":171},[75,4523,4524],{"class":77,"line":642},[75,4525,2165],{"class":85},[75,4527,4528],{"class":77,"line":663},[75,4529,2160],{"class":85},[17,4531,4532,4533,4536,4537,4536,4540,4543],{},"This is exactly the same as doing ",[49,4534,4535],{},"createSign(algorithm)"," + ",[49,4538,4539],{},".update(data)",[49,4541,4542],{},".sign(privateKey)",". It’s just a one-liner:",[66,4545,4547],{"className":2039,"code":4546,"language":2041,"meta":15,"style":15},"// Equivalent to above:\nconst signature = sign('SHA256', data, privateKeyObject);\n",[49,4548,4549,4554],{"__ignoreMap":15},[75,4550,4551],{"class":77,"line":78},[75,4552,4553],{"class":2542},"// Equivalent to above:\n",[75,4555,4556,4558,4560,4562,4564,4566,4568],{"class":77,"line":25},[75,4557,2490],{"class":81},[75,4559,2493],{"class":171},[75,4561,605],{"class":81},[75,4563,3329],{"class":190},[75,4565,2135],{"class":85},[75,4567,3887],{"class":92},[75,4569,4570],{"class":85},", data, privateKeyObject);\n",[58,4572,4574],{"id":4573},"cryptoverifyalgorithm-data-publickey-signature",[49,4575,4576],{},"crypto.verify(algorithm, data, publicKey, signature)",[17,4578,4579],{},[1748,4580,4427],{},[66,4582,4584],{"className":2039,"code":4583,"language":2041,"meta":15,"style":15},"import { verify, constants } from 'crypto';\nconst isOk = verify(\n  'RSA-SHA256',\n  Buffer.from('hello'),\n  {\n    key: publicKeyObject,  // or PEM string\n    padding: constants.RSA_PKCS1_PSS_PADDING,\n    saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n  },\n  signatureBuffer\n);\nconsole.log(isOk); // true or false\n",[49,4585,4586,4599,4612,4618,4631,4635,4643,4651,4657,4661,4666,4670],{"__ignoreMap":15},[75,4587,4588,4590,4593,4595,4597],{"class":77,"line":78},[75,4589,2593],{"class":81},[75,4591,4592],{"class":85}," { verify, constants } ",[75,4594,2599],{"class":81},[75,4596,2602],{"class":92},[75,4598,2605],{"class":85},[75,4600,4601,4603,4606,4608,4610],{"class":77,"line":25},[75,4602,2490],{"class":81},[75,4604,4605],{"class":171}," isOk",[75,4607,605],{"class":81},[75,4609,4015],{"class":190},[75,4611,2197],{"class":85},[75,4613,4614,4616],{"class":77,"line":215},[75,4615,4472],{"class":92},[75,4617,2205],{"class":85},[75,4619,4620,4622,4624,4626,4628],{"class":77,"line":221},[75,4621,4482],{"class":85},[75,4623,2599],{"class":190},[75,4625,2135],{"class":85},[75,4627,4489],{"class":92},[75,4629,4630],{"class":85},"),\n",[75,4632,4633],{"class":77,"line":235},[75,4634,4066],{"class":85},[75,4636,4637,4640],{"class":77,"line":248},[75,4638,4639],{"class":85},"    key: publicKeyObject,  ",[75,4641,4642],{"class":2542},"// or PEM string\n",[75,4644,4645,4647,4649],{"class":77,"line":261},[75,4646,4079],{"class":85},[75,4648,3423],{"class":171},[75,4650,2205],{"class":85},[75,4652,4653,4655],{"class":77,"line":274},[75,4654,4092],{"class":85},[75,4656,3560],{"class":171},[75,4658,4659],{"class":77,"line":615},[75,4660,2687],{"class":85},[75,4662,4663],{"class":77,"line":636},[75,4664,4665],{"class":85},"  signatureBuffer\n",[75,4667,4668],{"class":77,"line":642},[75,4669,2160],{"class":85},[75,4671,4672,4674,4676,4679],{"class":77,"line":663},[75,4673,3453],{"class":85},[75,4675,3456],{"class":190},[75,4677,4678],{"class":85},"(isOk); ",[75,4680,4681],{"class":2542},"// true or false\n",[17,4683,4684,4685,4536,4688,4536,4690,1802],{},"Internally, it does exactly ",[49,4686,4687],{},"createVerify(algorithm)",[49,4689,4539],{},[49,4691,4692],{},".verify(pubKey, sig)",[3960,4694,4695],{},[17,4696,4697],{},"If you only have a single piece of data to sign (rather than a streaming scenario), it’s simpler to use these one-line helpers.",[1414,4699],{},[1737,4701,4703],{"id":4702},"_7-other-publicprivate-keyobjectrelated-apis","7. Other “Public/Private” KeyObject–Related APIs",[17,4705,4706,4707,4709],{},"While “sign/verify” is one major use case, Node’s ",[49,4708,2400],{}," module supports a few other operations on asymmetric key pairs:",[58,4711,4713,2446,4716],{"id":4712},"cryptopublicencryptpublickey-buffer-cryptoprivatedecryptprivatekey-buffer",[49,4714,4715],{},"crypto.publicEncrypt(publicKey, buffer)",[49,4717,4718],{},"crypto.privateDecrypt(privateKey, buffer)",[1742,4720,4721,4727],{},[1745,4722,4723,4726],{},[1748,4724,4725],{},"Classic RSA encryption:"," encrypt with a recipient’s public key, decrypt with your private.",[1745,4728,4729,4732],{},[1748,4730,4731],{},"Note:"," RSA encryption is now generally discouraged for large payloads (you’d typically do hybrid‐encryption: generate a random AES key, encrypt data with AES, then encrypt AES key with RSA).",[17,4734,4735],{},[1748,4736,1991],{},[66,4738,4740],{"className":2039,"code":4739,"language":2041,"meta":15,"style":15},"import { publicEncrypt, privateDecrypt, constants } from 'crypto';\n\n// Encrypt:\nconst encrypted = publicEncrypt(\n  {\n    key: publicKeyObject,\n    padding: constants.RSA_PKCS1_OAEP_PADDING, // preferred over PKCS1 v1.5\n    oaepHash: 'sha256'                              // e.g. SHA-256 for OAEP\n  },\n  Buffer.from('secret data')\n);\n// Decrypt:\nconst decrypted = privateDecrypt(\n  {\n    key: privateKeyObject,\n    padding: constants.RSA_PKCS1_OAEP_PADDING,\n    oaepHash: 'sha256'\n  },\n  encrypted\n);\nconsole.log(decrypted.toString()); // 'secret data'\n",[49,4741,4742,4755,4759,4764,4778,4782,4787,4799,4810,4814,4827,4831,4836,4850,4854,4859,4867,4874,4878,4883,4887],{"__ignoreMap":15},[75,4743,4744,4746,4749,4751,4753],{"class":77,"line":78},[75,4745,2593],{"class":81},[75,4747,4748],{"class":85}," { publicEncrypt, privateDecrypt, constants } ",[75,4750,2599],{"class":81},[75,4752,2602],{"class":92},[75,4754,2605],{"class":85},[75,4756,4757],{"class":77,"line":25},[75,4758,527],{"emptyLinePlaceholder":30},[75,4760,4761],{"class":77,"line":215},[75,4762,4763],{"class":2542},"// Encrypt:\n",[75,4765,4766,4768,4771,4773,4776],{"class":77,"line":221},[75,4767,2490],{"class":81},[75,4769,4770],{"class":171}," encrypted",[75,4772,605],{"class":81},[75,4774,4775],{"class":190}," publicEncrypt",[75,4777,2197],{"class":85},[75,4779,4780],{"class":77,"line":235},[75,4781,4066],{"class":85},[75,4783,4784],{"class":77,"line":248},[75,4785,4786],{"class":85},"    key: publicKeyObject,\n",[75,4788,4789,4791,4794,4796],{"class":77,"line":261},[75,4790,4079],{"class":85},[75,4792,4793],{"class":171},"RSA_PKCS1_OAEP_PADDING",[75,4795,1520],{"class":85},[75,4797,4798],{"class":2542},"// preferred over PKCS1 v1.5\n",[75,4800,4801,4804,4807],{"class":77,"line":274},[75,4802,4803],{"class":85},"    oaepHash: ",[75,4805,4806],{"class":92},"'sha256'",[75,4808,4809],{"class":2542},"                              // e.g. SHA-256 for OAEP\n",[75,4811,4812],{"class":77,"line":615},[75,4813,2687],{"class":85},[75,4815,4816,4818,4820,4822,4825],{"class":77,"line":636},[75,4817,4482],{"class":85},[75,4819,2599],{"class":190},[75,4821,2135],{"class":85},[75,4823,4824],{"class":92},"'secret data'",[75,4826,585],{"class":85},[75,4828,4829],{"class":77,"line":642},[75,4830,2160],{"class":85},[75,4832,4833],{"class":77,"line":663},[75,4834,4835],{"class":2542},"// Decrypt:\n",[75,4837,4838,4840,4843,4845,4848],{"class":77,"line":669},[75,4839,2490],{"class":81},[75,4841,4842],{"class":171}," decrypted",[75,4844,605],{"class":81},[75,4846,4847],{"class":190}," privateDecrypt",[75,4849,2197],{"class":85},[75,4851,4852],{"class":77,"line":675},[75,4853,4066],{"class":85},[75,4855,4856],{"class":77,"line":684},[75,4857,4858],{"class":85},"    key: privateKeyObject,\n",[75,4860,4861,4863,4865],{"class":77,"line":690},[75,4862,4079],{"class":85},[75,4864,4793],{"class":171},[75,4866,2205],{"class":85},[75,4868,4869,4871],{"class":77,"line":2254},[75,4870,4803],{"class":85},[75,4872,4873],{"class":92},"'sha256'\n",[75,4875,4876],{"class":77,"line":2259},[75,4877,2687],{"class":85},[75,4879,4880],{"class":77,"line":2276},[75,4881,4882],{"class":85},"  encrypted\n",[75,4884,4885],{"class":77,"line":2294},[75,4886,2160],{"class":85},[75,4888,4889,4891,4893,4896,4898,4901],{"class":77,"line":2299},[75,4890,3453],{"class":85},[75,4892,3456],{"class":190},[75,4894,4895],{"class":85},"(decrypted.",[75,4897,3467],{"class":190},[75,4899,4900],{"class":85},"()); ",[75,4902,4903],{"class":2542},"// 'secret data'\n",[58,4905,4907,2446,4910],{"id":4906},"cryptoprivateencryptprivatekey-buffer-cryptopublicdecryptpublickey-buffer",[49,4908,4909],{},"crypto.privateEncrypt(privateKey, buffer)",[49,4911,4912],{},"crypto.publicDecrypt(publicKey, buffer)",[1742,4914,4915,4920],{},[1745,4916,4917,4918,1802],{},"Rarely used. “Private encrypt” is essentially “sign with raw RSA” (no hashing)—this is not a standard “signature” operation and should not be used in place of ",[49,4919,4391],{},[1745,4921,4922,4923,4926],{},"You’ll see ",[49,4924,4925],{},"crypto.privateEncrypt"," if you must interoperate with some legacy system that expects raw RSA cryptography.",[58,4928,4930],{"id":4929},"cryptodiffiehellman",[49,4931,4932],{},"crypto.diffieHellman()",[1742,4934,4935,4938],{},[1745,4936,4937],{},"For Diffie-Hellman key exchange: both parties derive a shared secret from each other’s public parameters.",[1745,4939,4940,4941,4944],{},"In modern code, you’d usually use ECDH (",[49,4942,4943],{},"crypto.createECDH(curveName)","), which returns a small, efficient key exchange mechanism (no need to deal with big integer math manually).",[17,4946,4947],{},[1748,4948,4949],{},"Example (ECDH P-256):",[66,4951,4953],{"className":2039,"code":4952,"language":2041,"meta":15,"style":15},"import { createECDH } from 'crypto';\n\n// Party A:\nconst alice = createECDH('prime256v1');\nalice.generateKeys();\nconst alicePub = alice.getPublicKey(); // send to Bob\n\n// Party B:\nconst bob = createECDH('prime256v1');\nbob.generateKeys();\nconst bobPub = bob.getPublicKey(); // send to Alice\n\n// Each side computes the shared secret:\nconst aliceShared = alice.computeSecret(bobPub);\nconst bobShared   = bob.computeSecret(alicePub);\nconsole.log(aliceShared.equals(bobShared)); // true\n",[49,4954,4955,4968,4972,4977,4995,5005,5025,5029,5034,5051,5060,5079,5083,5088,5105,5122],{"__ignoreMap":15},[75,4956,4957,4959,4962,4964,4966],{"class":77,"line":78},[75,4958,2593],{"class":81},[75,4960,4961],{"class":85}," { createECDH } ",[75,4963,2599],{"class":81},[75,4965,2602],{"class":92},[75,4967,2605],{"class":85},[75,4969,4970],{"class":77,"line":25},[75,4971,527],{"emptyLinePlaceholder":30},[75,4973,4974],{"class":77,"line":215},[75,4975,4976],{"class":2542},"// Party A:\n",[75,4978,4979,4981,4984,4986,4989,4991,4993],{"class":77,"line":221},[75,4980,2490],{"class":81},[75,4982,4983],{"class":171}," alice",[75,4985,605],{"class":81},[75,4987,4988],{"class":190}," createECDH",[75,4990,2135],{"class":85},[75,4992,2851],{"class":92},[75,4994,2160],{"class":85},[75,4996,4997,5000,5003],{"class":77,"line":235},[75,4998,4999],{"class":85},"alice.",[75,5001,5002],{"class":190},"generateKeys",[75,5004,2305],{"class":85},[75,5006,5007,5009,5012,5014,5017,5020,5022],{"class":77,"line":248},[75,5008,2490],{"class":81},[75,5010,5011],{"class":171}," alicePub",[75,5013,605],{"class":81},[75,5015,5016],{"class":85}," alice.",[75,5018,5019],{"class":190},"getPublicKey",[75,5021,3371],{"class":85},[75,5023,5024],{"class":2542},"// send to Bob\n",[75,5026,5027],{"class":77,"line":261},[75,5028,527],{"emptyLinePlaceholder":30},[75,5030,5031],{"class":77,"line":274},[75,5032,5033],{"class":2542},"// Party B:\n",[75,5035,5036,5038,5041,5043,5045,5047,5049],{"class":77,"line":615},[75,5037,2490],{"class":81},[75,5039,5040],{"class":171}," bob",[75,5042,605],{"class":81},[75,5044,4988],{"class":190},[75,5046,2135],{"class":85},[75,5048,2851],{"class":92},[75,5050,2160],{"class":85},[75,5052,5053,5056,5058],{"class":77,"line":636},[75,5054,5055],{"class":85},"bob.",[75,5057,5002],{"class":190},[75,5059,2305],{"class":85},[75,5061,5062,5064,5067,5069,5072,5074,5076],{"class":77,"line":642},[75,5063,2490],{"class":81},[75,5065,5066],{"class":171}," bobPub",[75,5068,605],{"class":81},[75,5070,5071],{"class":85}," bob.",[75,5073,5019],{"class":190},[75,5075,3371],{"class":85},[75,5077,5078],{"class":2542},"// send to Alice\n",[75,5080,5081],{"class":77,"line":663},[75,5082,527],{"emptyLinePlaceholder":30},[75,5084,5085],{"class":77,"line":669},[75,5086,5087],{"class":2542},"// Each side computes the shared secret:\n",[75,5089,5090,5092,5095,5097,5099,5102],{"class":77,"line":675},[75,5091,2490],{"class":81},[75,5093,5094],{"class":171}," aliceShared",[75,5096,605],{"class":81},[75,5098,5016],{"class":85},[75,5100,5101],{"class":190},"computeSecret",[75,5103,5104],{"class":85},"(bobPub);\n",[75,5106,5107,5109,5112,5115,5117,5119],{"class":77,"line":684},[75,5108,2490],{"class":81},[75,5110,5111],{"class":171}," bobShared",[75,5113,5114],{"class":81},"   =",[75,5116,5071],{"class":85},[75,5118,5101],{"class":190},[75,5120,5121],{"class":85},"(alicePub);\n",[75,5123,5124,5126,5128,5131,5134,5137],{"class":77,"line":690},[75,5125,3453],{"class":85},[75,5127,3456],{"class":190},[75,5129,5130],{"class":85},"(aliceShared.",[75,5132,5133],{"class":190},"equals",[75,5135,5136],{"class":85},"(bobShared)); ",[75,5138,5139],{"class":2542},"// true\n",[58,5141,5143,140,5146],{"id":5142},"cryptogeneratekeypair-and-cryptogeneratekeypairsync",[49,5144,5145],{},"crypto.generateKeyPair()",[49,5147,5148],{},"crypto.generateKeyPairSync()",[1742,5150,5151,5157],{},[1745,5152,5153,5154,5156],{},"Synchronous and asynchronous ways to generate new key pairs. We used ",[49,5155,2860],{}," above.",[1745,5158,5159],{},"If you need to generate keys at runtime without blocking the event loop, call:",[66,5161,5163],{"className":2039,"code":5162,"language":2041,"meta":15,"style":15},"import { generateKeyPair } from 'crypto';\ngenerateKeyPair('rsa', {\n  modulusLength: 2048,\n  publicKeyEncoding: { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'pass' }\n}, (err, pubPem, privPem) => {\n  if (err) throw err;\n  // pubPem / privPem are PEM strings\n});\n",[49,5164,5165,5178,5188,5196,5209,5231,5255,5268,5273],{"__ignoreMap":15},[75,5166,5167,5169,5172,5174,5176],{"class":77,"line":78},[75,5168,2593],{"class":81},[75,5170,5171],{"class":85}," { generateKeyPair } ",[75,5173,2599],{"class":81},[75,5175,2602],{"class":92},[75,5177,2605],{"class":85},[75,5179,5180,5182,5184,5186],{"class":77,"line":25},[75,5181,2435],{"class":190},[75,5183,2135],{"class":85},[75,5185,2642],{"class":92},[75,5187,2645],{"class":85},[75,5189,5190,5192,5194],{"class":77,"line":215},[75,5191,2650],{"class":85},[75,5193,2653],{"class":171},[75,5195,2205],{"class":85},[75,5197,5198,5201,5203,5205,5207],{"class":77,"line":221},[75,5199,5200],{"class":85},"  publicKeyEncoding: { type: ",[75,5202,3042],{"class":92},[75,5204,3771],{"class":85},[75,5206,2710],{"class":92},[75,5208,3776],{"class":85},[75,5210,5211,5213,5215,5217,5219,5222,5224,5226,5229],{"class":77,"line":235},[75,5212,3781],{"class":85},[75,5214,3784],{"class":92},[75,5216,3771],{"class":85},[75,5218,2710],{"class":92},[75,5220,5221],{"class":85},", cipher: ",[75,5223,2720],{"class":92},[75,5225,4274],{"class":85},[75,5227,5228],{"class":92},"'pass'",[75,5230,3791],{"class":85},[75,5232,5233,5236,5239,5241,5244,5246,5249,5251,5253],{"class":77,"line":248},[75,5234,5235],{"class":85},"}, (",[75,5237,5238],{"class":501},"err",[75,5240,1520],{"class":85},[75,5242,5243],{"class":501},"pubPem",[75,5245,1520],{"class":85},[75,5247,5248],{"class":501},"privPem",[75,5250,2070],{"class":85},[75,5252,2073],{"class":81},[75,5254,2076],{"class":85},[75,5256,5257,5259,5262,5265],{"class":77,"line":261},[75,5258,532],{"class":81},[75,5260,5261],{"class":85}," (err) ",[75,5263,5264],{"class":81},"throw",[75,5266,5267],{"class":85}," err;\n",[75,5269,5270],{"class":77,"line":274},[75,5271,5272],{"class":2542},"  // pubPem / privPem are PEM strings\n",[75,5274,5275],{"class":77,"line":615},[75,5276,2311],{"class":85},[1414,5278],{},[1737,5280,5282],{"id":5281},"_8-putting-it-all-together-a-minimal-rsa-pss-example","8. Putting It All Together: A Minimal RSA-PSS Example",[17,5284,5285],{},"Below is a concise end-to-end example (no unnecessary extras) showing:",[3245,5287,5288,5291,5296,5303],{},[1745,5289,5290],{},"Generating an RSA key pair (synchronous, at startup).",[1745,5292,5293,5294,2410],{},"Wrapping them in ",[49,5295,2404],{},[1745,5297,5298,5299,5302],{},"Using ",[49,5300,5301],{},"crypto.sign"," to create an RSA-PSS signature.",[1745,5304,5298,5305,5308],{},[49,5306,5307],{},"crypto.verify"," to check that signature.",[66,5310,5312],{"className":2039,"code":5311,"language":2041,"meta":15,"style":15},"// file: rsa_pss_demo.js\nimport fs from 'fs';\nimport { generateKeyPairSync, createPrivateKey, createPublicKey, sign, verify, constants } from 'crypto';\n\n//\n// 1. Generate a new key pair (do this once; you could instead load from PEM on disk)\n//\nconst { publicKey: pubPem, privateKey: privPem } = generateKeyPairSync('rsa', {\n  modulusLength: 2048,\n  publicKeyEncoding:  { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' } // no passphrase in this example\n});\n\n// (Optional) Persist to disk so you can reuse them later:\nfs.writeFileSync('private-key.pem', privPem);\nfs.writeFileSync('public-key.pem', pubPem);\n\n// 2. Wrap the PEMs into KeyObjects\nconst privateKeyObj = createPrivateKey({ key: privPem, format: 'pem' });\nconst publicKeyObj  = createPublicKey({ key: pubPem, format: 'pem' });\n\n//\n// 3. The data you want to sign:\nconst message = Buffer.from('Hello, world! This must be signed.');\n\n// 4. Sign with RSA-PSS (SHA-256 hash, PSS padding)\nconst signature = sign('sha256', message, {\n  key: privateKeyObj,\n  padding: constants.RSA_PKCS1_PSS_PADDING,\n  saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n});\n\n// 5. (Optional) Transmit or store `signature` (e.g. base64-encode it for JSON):\nconst signatureBase64 = signature.toString('base64');\nconsole.log('Signature (Base64):', signatureBase64);\n\n//\n// At the receiver or later in your code, verify:\nconst incomingSignature = Buffer.from(signatureBase64, 'base64');\n\nconst isValid = verify('sha256', message, {\n  key: publicKeyObj,\n  padding: constants.RSA_PKCS1_PSS_PADDING,\n  saltLength: constants.RSA_PSS_SALTLEN_DIGEST\n}, incomingSignature);\n\nconsole.log('Signature valid?', isValid); // → true\n",[49,5313,5314,5319,5331,5344,5348,5353,5358,5362,5394,5402,5414,5429,5433,5437,5442,5458,5472,5476,5481,5499,5516,5520,5524,5529,5549,5553,5558,5575,5580,5588,5594,5598,5602,5608,5628,5643,5648,5653,5659,5680,5685,5702,5708,5717,5724,5730,5735],{"__ignoreMap":15},[75,5315,5316],{"class":77,"line":78},[75,5317,5318],{"class":2542},"// file: rsa_pss_demo.js\n",[75,5320,5321,5323,5325,5327,5329],{"class":77,"line":25},[75,5322,2593],{"class":81},[75,5324,2978],{"class":85},[75,5326,2599],{"class":81},[75,5328,2983],{"class":92},[75,5330,2605],{"class":85},[75,5332,5333,5335,5338,5340,5342],{"class":77,"line":215},[75,5334,2593],{"class":81},[75,5336,5337],{"class":85}," { generateKeyPairSync, createPrivateKey, createPublicKey, sign, verify, constants } ",[75,5339,2599],{"class":81},[75,5341,2602],{"class":92},[75,5343,2605],{"class":85},[75,5345,5346],{"class":77,"line":221},[75,5347,527],{"emptyLinePlaceholder":30},[75,5349,5350],{"class":77,"line":235},[75,5351,5352],{"class":2542},"//\n",[75,5354,5355],{"class":77,"line":248},[75,5356,5357],{"class":2542},"// 1. Generate a new key pair (do this once; you could instead load from PEM on disk)\n",[75,5359,5360],{"class":77,"line":261},[75,5361,5352],{"class":2542},[75,5363,5364,5366,5368,5370,5372,5374,5376,5378,5380,5382,5384,5386,5388,5390,5392],{"class":77,"line":274},[75,5365,2490],{"class":81},[75,5367,2621],{"class":85},[75,5369,2624],{"class":501},[75,5371,3179],{"class":85},[75,5373,5243],{"class":171},[75,5375,1520],{"class":85},[75,5377,2629],{"class":501},[75,5379,3179],{"class":85},[75,5381,5248],{"class":171},[75,5383,2632],{"class":85},[75,5385,89],{"class":81},[75,5387,2637],{"class":190},[75,5389,2135],{"class":85},[75,5391,2642],{"class":92},[75,5393,2645],{"class":85},[75,5395,5396,5398,5400],{"class":77,"line":615},[75,5397,2650],{"class":85},[75,5399,2653],{"class":171},[75,5401,2205],{"class":85},[75,5403,5404,5406,5408,5410,5412],{"class":77,"line":636},[75,5405,3766],{"class":85},[75,5407,3042],{"class":92},[75,5409,3771],{"class":85},[75,5411,2710],{"class":92},[75,5413,3776],{"class":85},[75,5415,5416,5418,5420,5422,5424,5426],{"class":77,"line":642},[75,5417,3781],{"class":85},[75,5419,3784],{"class":92},[75,5421,3771],{"class":85},[75,5423,2710],{"class":92},[75,5425,2632],{"class":85},[75,5427,5428],{"class":2542},"// no passphrase in this example\n",[75,5430,5431],{"class":77,"line":663},[75,5432,2311],{"class":85},[75,5434,5435],{"class":77,"line":669},[75,5436,527],{"emptyLinePlaceholder":30},[75,5438,5439],{"class":77,"line":675},[75,5440,5441],{"class":2542},"// (Optional) Persist to disk so you can reuse them later:\n",[75,5443,5444,5447,5450,5452,5455],{"class":77,"line":684},[75,5445,5446],{"class":85},"fs.",[75,5448,5449],{"class":190},"writeFileSync",[75,5451,2135],{"class":85},[75,5453,5454],{"class":92},"'private-key.pem'",[75,5456,5457],{"class":85},", privPem);\n",[75,5459,5460,5462,5464,5466,5469],{"class":77,"line":690},[75,5461,5446],{"class":85},[75,5463,5449],{"class":190},[75,5465,2135],{"class":85},[75,5467,5468],{"class":92},"'public-key.pem'",[75,5470,5471],{"class":85},", pubPem);\n",[75,5473,5474],{"class":77,"line":2254},[75,5475,527],{"emptyLinePlaceholder":30},[75,5477,5478],{"class":77,"line":2259},[75,5479,5480],{"class":2542},"// 2. Wrap the PEMs into KeyObjects\n",[75,5482,5483,5485,5488,5490,5492,5495,5497],{"class":77,"line":2276},[75,5484,2490],{"class":81},[75,5486,5487],{"class":171}," privateKeyObj",[75,5489,605],{"class":81},[75,5491,2781],{"class":190},[75,5493,5494],{"class":85},"({ key: privPem, format: ",[75,5496,2710],{"class":92},[75,5498,3144],{"class":85},[75,5500,5501,5503,5506,5508,5510,5512,5514],{"class":77,"line":2294},[75,5502,2490],{"class":81},[75,5504,5505],{"class":171}," publicKeyObj",[75,5507,3155],{"class":81},[75,5509,2825],{"class":190},[75,5511,4374],{"class":85},[75,5513,2710],{"class":92},[75,5515,3144],{"class":85},[75,5517,5518],{"class":77,"line":2299},[75,5519,527],{"emptyLinePlaceholder":30},[75,5521,5522],{"class":77,"line":2308},[75,5523,5352],{"class":2542},[75,5525,5526],{"class":77,"line":2792},[75,5527,5528],{"class":2542},"// 3. The data you want to sign:\n",[75,5530,5531,5533,5536,5538,5540,5542,5544,5547],{"class":77,"line":2802},[75,5532,2490],{"class":81},[75,5534,5535],{"class":171}," message",[75,5537,605],{"class":81},[75,5539,3300],{"class":85},[75,5541,2599],{"class":190},[75,5543,2135],{"class":85},[75,5545,5546],{"class":92},"'Hello, world! This must be signed.'",[75,5548,2160],{"class":85},[75,5550,5551],{"class":77,"line":2810},[75,5552,527],{"emptyLinePlaceholder":30},[75,5554,5555],{"class":77,"line":2815},[75,5556,5557],{"class":2542},"// 4. Sign with RSA-PSS (SHA-256 hash, PSS padding)\n",[75,5559,5560,5562,5564,5566,5568,5570,5572],{"class":77,"line":3092},[75,5561,2490],{"class":81},[75,5563,2493],{"class":171},[75,5565,605],{"class":81},[75,5567,3329],{"class":190},[75,5569,2135],{"class":85},[75,5571,4806],{"class":92},[75,5573,5574],{"class":85},", message, {\n",[75,5576,5577],{"class":77,"line":3103},[75,5578,5579],{"class":85},"  key: privateKeyObj,\n",[75,5581,5582,5584,5586],{"class":77,"line":3114},[75,5583,3420],{"class":85},[75,5585,3423],{"class":171},[75,5587,2205],{"class":85},[75,5589,5590,5592],{"class":77,"line":3120},[75,5591,3433],{"class":85},[75,5593,3560],{"class":171},[75,5595,5596],{"class":77,"line":3126},[75,5597,2311],{"class":85},[75,5599,5600],{"class":77,"line":3147},[75,5601,527],{"emptyLinePlaceholder":30},[75,5603,5605],{"class":77,"line":5604},33,[75,5606,5607],{"class":2542},"// 5. (Optional) Transmit or store `signature` (e.g. base64-encode it for JSON):\n",[75,5609,5611,5613,5615,5617,5620,5622,5624,5626],{"class":77,"line":5610},34,[75,5612,2490],{"class":81},[75,5614,3659],{"class":171},[75,5616,605],{"class":81},[75,5618,5619],{"class":85}," signature.",[75,5621,3467],{"class":190},[75,5623,2135],{"class":85},[75,5625,3472],{"class":92},[75,5627,2160],{"class":85},[75,5629,5631,5633,5635,5637,5640],{"class":77,"line":5630},35,[75,5632,3453],{"class":85},[75,5634,3456],{"class":190},[75,5636,2135],{"class":85},[75,5638,5639],{"class":92},"'Signature (Base64):'",[75,5641,5642],{"class":85},", signatureBase64);\n",[75,5644,5646],{"class":77,"line":5645},36,[75,5647,527],{"emptyLinePlaceholder":30},[75,5649,5651],{"class":77,"line":5650},37,[75,5652,5352],{"class":2542},[75,5654,5656],{"class":77,"line":5655},38,[75,5657,5658],{"class":2542},"// At the receiver or later in your code, verify:\n",[75,5660,5662,5664,5667,5669,5671,5673,5676,5678],{"class":77,"line":5661},39,[75,5663,2490],{"class":81},[75,5665,5666],{"class":171}," incomingSignature",[75,5668,605],{"class":81},[75,5670,3300],{"class":85},[75,5672,2599],{"class":190},[75,5674,5675],{"class":85},"(signatureBase64, ",[75,5677,3472],{"class":92},[75,5679,2160],{"class":85},[75,5681,5683],{"class":77,"line":5682},40,[75,5684,527],{"emptyLinePlaceholder":30},[75,5686,5688,5690,5692,5694,5696,5698,5700],{"class":77,"line":5687},41,[75,5689,2490],{"class":81},[75,5691,2528],{"class":171},[75,5693,605],{"class":81},[75,5695,4015],{"class":190},[75,5697,2135],{"class":85},[75,5699,4806],{"class":92},[75,5701,5574],{"class":85},[75,5703,5705],{"class":77,"line":5704},42,[75,5706,5707],{"class":85},"  key: publicKeyObj,\n",[75,5709,5711,5713,5715],{"class":77,"line":5710},43,[75,5712,3420],{"class":85},[75,5714,3423],{"class":171},[75,5716,2205],{"class":85},[75,5718,5720,5722],{"class":77,"line":5719},44,[75,5721,3433],{"class":85},[75,5723,3560],{"class":171},[75,5725,5727],{"class":77,"line":5726},45,[75,5728,5729],{"class":85},"}, incomingSignature);\n",[75,5731,5733],{"class":77,"line":5732},46,[75,5734,527],{"emptyLinePlaceholder":30},[75,5736,5738,5740,5742,5744,5746,5749],{"class":77,"line":5737},47,[75,5739,3453],{"class":85},[75,5741,3456],{"class":190},[75,5743,2135],{"class":85},[75,5745,4125],{"class":92},[75,5747,5748],{"class":85},", isValid); ",[75,5750,5751],{"class":2542},"// → true\n",[1742,5753,5754,5770,5773],{},[1745,5755,5756,5757,5760,5761,4536,5763,4536,5766,5769],{},"We used ",[49,5758,5759],{},"sign('sha256', data, options)"," instead of manually ",[49,5762,3240],{},[49,5764,5765],{},".update",[49,5767,5768],{},".sign",", because there’s only one chunk of data here.",[1745,5771,5772],{},"We explicitly chose RSA-PSS (more secure than PKCS#1 v1.5 for new applications).",[1745,5774,5775,5776,1802],{},"We did not encrypt the private PEM (for simplicity), but in production, store the private key on disk (or secret manager) with a passphrase and load it with ",[49,5777,5778],{},"createPrivateKey({ key: encryptedPem, passphrase: ... })",[1414,5780],{},[1737,5782,5784],{"id":5783},"_9-other-sign-like-operations","9. Other “Sign-like” Operations",[17,5786,5787,5788,5790],{},"Node’s ",[49,5789,2400],{}," module also exposes:",[1742,5792,5793,5808,5816],{},[1745,5794,5795,5797,5798,5800,5801,5804,5805,1802],{},[49,5796,2479],{}," — returns a ",[49,5799,3252],{}," object. Use this when you need to feed data in multiple chunks (e.g. streaming a large file). After streaming all chunks via ",[49,5802,5803],{},".update()",", call ",[49,5806,5807],{},".sign(privateKey, [outputEncoding])",[1745,5809,5810,5797,5812,5815],{},[49,5811,2515],{},[49,5813,5814],{},"Verify"," object for stream-signature verification.",[1745,5817,5818,5821],{},[49,5819,5820],{},"crypto.createHmac(algorithm, key)"," — computes an HMAC of data (symmetric). Not a public/private operation. Use it when you and a known party share a secret key and just want to ensure integrity.",[17,5823,5824],{},[1748,5825,1991],{},[66,5827,5829],{"className":2039,"code":5828,"language":2041,"meta":15,"style":15},"import { createHmac } from 'crypto';\nconst hmac = createHmac('sha256', Buffer.from('my-shared-secret'));\nhmac.update('some message');\nconst digest = hmac.digest('hex');\n// Send `digest` alongside your message. The receiver recomputes using the same shared key.\n",[49,5830,5831,5844,5872,5886,5907],{"__ignoreMap":15},[75,5832,5833,5835,5838,5840,5842],{"class":77,"line":78},[75,5834,2593],{"class":81},[75,5836,5837],{"class":85}," { createHmac } ",[75,5839,2599],{"class":81},[75,5841,2602],{"class":92},[75,5843,2605],{"class":85},[75,5845,5846,5848,5851,5853,5856,5858,5860,5863,5865,5867,5870],{"class":77,"line":25},[75,5847,2490],{"class":81},[75,5849,5850],{"class":171}," hmac",[75,5852,605],{"class":81},[75,5854,5855],{"class":190}," createHmac",[75,5857,2135],{"class":85},[75,5859,4806],{"class":92},[75,5861,5862],{"class":85},", Buffer.",[75,5864,2599],{"class":190},[75,5866,2135],{"class":85},[75,5868,5869],{"class":92},"'my-shared-secret'",[75,5871,3475],{"class":85},[75,5873,5874,5877,5879,5881,5884],{"class":77,"line":215},[75,5875,5876],{"class":85},"hmac.",[75,5878,3358],{"class":190},[75,5880,2135],{"class":85},[75,5882,5883],{"class":92},"'some message'",[75,5885,2160],{"class":85},[75,5887,5888,5890,5893,5895,5898,5901,5903,5905],{"class":77,"line":221},[75,5889,2490],{"class":81},[75,5891,5892],{"class":171}," digest",[75,5894,605],{"class":81},[75,5896,5897],{"class":85}," hmac.",[75,5899,5900],{"class":190},"digest",[75,5902,2135],{"class":85},[75,5904,3640],{"class":92},[75,5906,2160],{"class":85},[75,5908,5909],{"class":77,"line":235},[75,5910,5911],{"class":2542},"// Send `digest` alongside your message. The receiver recomputes using the same shared key.\n",[1414,5913],{},[1737,5915,5917],{"id":5916},"_10-best-practices-security-considerations","10. Best Practices & Security Considerations",[1742,5919,5920,5926,5932,5935,5949,5984,6043,6069,6083],{},[1745,5921,5922,5925],{},[1748,5923,5924],{},"Never hard-code private keys in your source."," Load them from a secure location (environment variable, container secret, secret manager).",[1745,5927,2857,5928,5931],{},[49,5929,5930],{},"createPrivateKey({ key: …, passphrase: … })"," if your PEM is encrypted.",[1745,5933,5934],{},"Prefer RSA-PSS (with SHA-256 or SHA-512) for signatures over plain PKCS#1 v1.5, unless you have to interoperate with an older system expecting v1.5. PSS is provably secure under modern standards.",[1745,5936,5937,5938,2446,5940,5943,5944,991,5946,5948],{},"Unless you need streaming signing, use the one-liner ",[49,5939,3220],{},[49,5941,5942],{},"crypto.verify(...)"," instead of managing a ",[49,5945,3252],{},[49,5947,5814],{}," instance manually. It’s less code and less room for mistakes.",[1745,5950,2857,5951,5953,5954,5956,5957],{},[49,5952,2404],{}," instead of raw PEM/",[49,5955,2421],{}," where possible. This prevents accidental exposure or leaking of the raw key material in logs.",[66,5958,5960],{"className":2039,"code":5959,"language":2041,"meta":15,"style":15},"const privObj = createPrivateKey({ key: myPem, format: 'pem' });\n// Now pass `privObj` to sign/verify/encrypt/etc.—never pass the PEM again.\n",[49,5961,5962,5979],{"__ignoreMap":15},[75,5963,5964,5966,5968,5970,5972,5975,5977],{"class":77,"line":78},[75,5965,2490],{"class":81},[75,5967,4262],{"class":171},[75,5969,605],{"class":81},[75,5971,2781],{"class":190},[75,5973,5974],{"class":85},"({ key: myPem, format: ",[75,5976,2710],{"class":92},[75,5978,3144],{"class":85},[75,5980,5981],{"class":77,"line":25},[75,5982,5983],{"class":2542},"// Now pass `privObj` to sign/verify/encrypt/etc.—never pass the PEM again.\n",[1745,5985,5986,5989,5990,6037,6040,6041,2030],{},[1748,5987,5988],{},"Store keys securely at rest."," If you must store a private key on disk, encrypt the PEM with a strong passphrase:",[66,5991,5993],{"className":2039,"code":5992,"language":2041,"meta":15,"style":15},"generateKeyPairSync('rsa', {\n  /* … */,\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: '…' }\n});\n",[49,5994,5995,6005,6012,6033],{"__ignoreMap":15},[75,5996,5997,5999,6001,6003],{"class":77,"line":78},[75,5998,2438],{"class":190},[75,6000,2135],{"class":85},[75,6002,2642],{"class":92},[75,6004,2645],{"class":85},[75,6006,6007,6010],{"class":77,"line":25},[75,6008,6009],{"class":2542},"  /* … */",[75,6011,2205],{"class":85},[75,6013,6014,6016,6018,6020,6022,6024,6026,6028,6031],{"class":77,"line":215},[75,6015,3781],{"class":85},[75,6017,3784],{"class":92},[75,6019,3771],{"class":85},[75,6021,2710],{"class":92},[75,6023,5221],{"class":85},[75,6025,2720],{"class":92},[75,6027,4274],{"class":85},[75,6029,6030],{"class":92},"'…'",[75,6032,3791],{"class":85},[75,6034,6035],{"class":77,"line":221},[75,6036,2311],{"class":85},[6038,6039],"br",{},"Or use a cloud KMS / hardware security module (HSM) so your application never directly touches the raw private key. Node can be configured to use signer callbacks (e.g., with AWS KMS, you call sign via an API instead of passing a local ",[49,6042,2404],{},[1745,6044,6045,6046,6049,6050,6049,6053,6056,6057,6060,6061,6049,6064,6049,6067,1802],{},"Be aware of data‐inclusion sequences. Always do ",[49,6047,6048],{},"sign.update(data)"," → ",[49,6051,6052],{},"sign.end()",[49,6054,6055],{},"sign.sign(...)",". If you mix in different data or call ",[49,6058,6059],{},"sign.sign"," prematurely, the output is invalid. Similarly for verify: ",[49,6062,6063],{},"verify.update(...)",[49,6065,6066],{},"verify.end()",[49,6068,4138],{},[1745,6070,6071,6072,6075,6076,52,6079,6082],{},"Select a secure hash (",[49,6073,6074],{},"SHA-256"," or above). Never use ",[49,6077,6078],{},"MD5",[49,6080,6081],{},"SHA-1"," for new designs.",[1745,6084,6085,6086,6089],{},"If you only need integrity/authenticity (not non-repudiation) in a two-party setting, consider HMAC (",[49,6087,6088],{},"createHmac",") instead: fewer moving parts and easier key management. Only use asymmetric signing when you must publish a public key for verification by third parties.",[1414,6091],{},[1737,6093,6095],{"id":6094},"_11-quick-reference-table","11. Quick Reference Table",[1464,6097,6098,6110],{},[1467,6099,6100],{},[1470,6101,6102,6104,6107],{},[1473,6103,2387],{},[1473,6105,6106],{},"Purpose",[1473,6108,6109],{},"Returns",[1481,6111,6112,6128,6143,6158,6173,6191,6212,6227,6245,6259,6273],{},[1470,6113,6114,6119,6122],{},[1486,6115,6116],{},[49,6117,6118],{},"generateKeyPairSync(type, options)",[1486,6120,6121],{},"Generate a new asymmetric key pair (RSA, EC, etc.)",[1486,6123,6124,6127],{},[49,6125,6126],{},"{ publicKey, privateKey }"," (PEM/DER strings)",[1470,6129,6130,6135,6140],{},[1486,6131,6132],{},[49,6133,6134],{},"generateKeyPair(type, options, callback)",[1486,6136,6137,6138],{},"Asynchronous version of ",[49,6139,2438],{},[1486,6141,6142],{},"via callback",[1470,6144,6145,6149,6154],{},[1486,6146,6147],{},[49,6148,4160],{},[1486,6150,6151,6152],{},"Import a private key (PEM/DER/JWK) into a ",[49,6153,2404],{},[1486,6155,6156],{},[49,6157,2404],{},[1470,6159,6160,6164,6169],{},[1486,6161,6162],{},[49,6163,4293],{},[1486,6165,6166,6167],{},"Import a public key (PEM/DER/JWK) into a ",[49,6168,2404],{},[1486,6170,6171],{},[49,6172,2404],{},[1470,6174,6175,6180,6186],{},[1486,6176,6177],{},[49,6178,6179],{},"sign(algorithm, data, privateKey[, encoding])",[1486,6181,6182,6183,6185],{},"One-liner: hash data, sign with private key, return ",[49,6184,2421],{}," or encoded string",[1486,6187,6188,6190],{},[49,6189,2421],{}," or string",[1470,6192,6193,6197,6207],{},[1486,6194,6195],{},[49,6196,4535],{},[1486,6198,6199,6200,6203,6204],{},"Streamable \"Sign\" instance: call ",[49,6201,6202],{},".update(...)",", then ",[49,6205,6206],{},".sign(...)",[1486,6208,6209,6211],{},[49,6210,3252],{}," instance",[1470,6213,6214,6219,6222],{},[1486,6215,6216],{},[49,6217,6218],{},"verify(algorithm, data, publicKey, signature)",[1486,6220,6221],{},"One-liner: verify signature against data and public key",[1486,6223,6224],{},[49,6225,6226],{},"boolean",[1470,6228,6229,6233,6241],{},[1486,6230,6231],{},[49,6232,4687],{},[1486,6234,6235,6236,6203,6238],{},"Streamable \"Verify\" instance: call ",[49,6237,6202],{},[49,6239,6240],{},".verify(...)",[1486,6242,6243,6211],{},[49,6244,5814],{},[1470,6246,6247,6252,6255],{},[1486,6248,6249],{},[49,6250,6251],{},"publicEncrypt(options, buffer)",[1486,6253,6254],{},"Encrypt with public key (usually RSA-OAEP)",[1486,6256,6257],{},[49,6258,2421],{},[1470,6260,6261,6266,6269],{},[1486,6262,6263],{},[49,6264,6265],{},"privateDecrypt(options, buffer)",[1486,6267,6268],{},"Decrypt with private key (usually RSA-OAEP)",[1486,6270,6271],{},[49,6272,2421],{},[1470,6274,6275,6280,6283],{},[1486,6276,6277],{},[49,6278,6279],{},"createHmac(algorithm, key)",[1486,6281,6282],{},"Compute an HMAC over data chunks—symmetric MAC",[1486,6284,6285,6211],{},[49,6286,6287],{},"Hmac",[1414,6289],{},[58,6291,6293],{"id":6292},"encrypting-data-with-nodejs","Encrypting Data With Node.js",[17,6295,6296],{},"In a recent side project I came upon the need to securely store credentials in a database that I can later retrieve and use as part of the application. I know that Node.js has a very extensive crypto module, so I knew it was possible, though I wasn’t sure on the specifics.",[17,6298,6299],{},"Typically when storing passwords or other similar secrets, values are hashed but in my case I needed to encrypt/decrypt data since I need to access the original value after accessing it from my database. To make this work, we can use the createCipheriv function along with a secret key and initialization vector (IV) to encrypt and decrypt our data.",[66,6301,6303],{"className":2039,"code":6302,"language":2041,"meta":15,"style":15},"import crypto from \"node:crypto\"\n\nconst algorithm = \"aes-256-cbc\"\nconst secretKey = process.env.SECRET_KEY\n\nif (!secretKey) {\n  throw new Error(\"SECRET_KEY environment variable is required\")\n}\n\nconst key = crypto\n  .createHash(\"sha512\")\n  .update(secretKey)\n  .digest(\"hex\")\n  .substring(0, 32)\n\nconst iv = crypto.randomBytes(16)\n\nexport function encrypt(data: string) {\n  const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv)\n  let encrypted = cipher.update(data, \"utf-8\", \"hex\")\n  encrypted += cipher.final(\"hex\")\n\n  // Package the IV and encrypted data together so it can be stored in a single\n  // column in the database.\n  return iv.toString(\"hex\") + encrypted\n}\n\nexport function decrypt(data: string) {\n  // Unpackage the combined iv + encrypted message. Since we are using a fixed\n  // size IV, we can hard code the slice length.\n  const inputIV = data.slice(0, 32)\n  const encrypted = data.slice(32)\n  const decipher = crypto.createDecipheriv(\n    algorithm,\n    Buffer.from(key),\n    Buffer.from(inputIV, \"hex\"),\n  )\n\n  let decrypted = decipher.update(encrypted, \"hex\", \"utf-8\")\n  decrypted += decipher.final(\"utf-8\")\n  return decrypted\n}\n",[49,6304,6305,6317,6321,6333,6348,6352,6365,6383,6387,6391,6403,6418,6427,6440,6459,6463,6485,6489,6512,6534,6561,6580,6584,6589,6594,6616,6620,6624,6643,6648,6653,6678,6696,6712,6717,6727,6740,6745,6749,6774,6791,6798],{"__ignoreMap":15},[75,6306,6307,6309,6312,6314],{"class":77,"line":78},[75,6308,2593],{"class":81},[75,6310,6311],{"class":85}," crypto ",[75,6313,2599],{"class":81},[75,6315,6316],{"class":92}," \"node:crypto\"\n",[75,6318,6319],{"class":77,"line":25},[75,6320,527],{"emptyLinePlaceholder":30},[75,6322,6323,6325,6328,6330],{"class":77,"line":215},[75,6324,2490],{"class":81},[75,6326,6327],{"class":171}," algorithm",[75,6329,605],{"class":81},[75,6331,6332],{"class":92}," \"aes-256-cbc\"\n",[75,6334,6335,6337,6340,6342,6345],{"class":77,"line":221},[75,6336,2490],{"class":81},[75,6338,6339],{"class":171}," secretKey",[75,6341,605],{"class":81},[75,6343,6344],{"class":85}," process.env.",[75,6346,6347],{"class":171},"SECRET_KEY\n",[75,6349,6350],{"class":77,"line":235},[75,6351,527],{"emptyLinePlaceholder":30},[75,6353,6354,6356,6359,6362],{"class":77,"line":248},[75,6355,154],{"class":81},[75,6357,6358],{"class":85}," (",[75,6360,6361],{"class":81},"!",[75,6363,6364],{"class":85},"secretKey) {\n",[75,6366,6367,6370,6373,6376,6378,6381],{"class":77,"line":261},[75,6368,6369],{"class":81},"  throw",[75,6371,6372],{"class":81}," new",[75,6374,6375],{"class":190}," Error",[75,6377,2135],{"class":85},[75,6379,6380],{"class":92},"\"SECRET_KEY environment variable is required\"",[75,6382,585],{"class":85},[75,6384,6385],{"class":77,"line":274},[75,6386,325],{"class":85},[75,6388,6389],{"class":77,"line":615},[75,6390,527],{"emptyLinePlaceholder":30},[75,6392,6393,6395,6398,6400],{"class":77,"line":636},[75,6394,2490],{"class":81},[75,6396,6397],{"class":171}," key",[75,6399,605],{"class":81},[75,6401,6402],{"class":85}," crypto\n",[75,6404,6405,6408,6411,6413,6416],{"class":77,"line":642},[75,6406,6407],{"class":85},"  .",[75,6409,6410],{"class":190},"createHash",[75,6412,2135],{"class":85},[75,6414,6415],{"class":92},"\"sha512\"",[75,6417,585],{"class":85},[75,6419,6420,6422,6424],{"class":77,"line":663},[75,6421,6407],{"class":85},[75,6423,3358],{"class":190},[75,6425,6426],{"class":85},"(secretKey)\n",[75,6428,6429,6431,6433,6435,6438],{"class":77,"line":669},[75,6430,6407],{"class":85},[75,6432,5900],{"class":190},[75,6434,2135],{"class":85},[75,6436,6437],{"class":92},"\"hex\"",[75,6439,585],{"class":85},[75,6441,6442,6444,6447,6449,6452,6454,6457],{"class":77,"line":675},[75,6443,6407],{"class":85},[75,6445,6446],{"class":190},"substring",[75,6448,2135],{"class":85},[75,6450,6451],{"class":171},"0",[75,6453,1520],{"class":85},[75,6455,6456],{"class":171},"32",[75,6458,585],{"class":85},[75,6460,6461],{"class":77,"line":684},[75,6462,527],{"emptyLinePlaceholder":30},[75,6464,6465,6467,6470,6472,6475,6478,6480,6483],{"class":77,"line":690},[75,6466,2490],{"class":81},[75,6468,6469],{"class":171}," iv",[75,6471,605],{"class":81},[75,6473,6474],{"class":85}," crypto.",[75,6476,6477],{"class":190},"randomBytes",[75,6479,2135],{"class":85},[75,6481,6482],{"class":171},"16",[75,6484,585],{"class":85},[75,6486,6487],{"class":77,"line":2254},[75,6488,527],{"emptyLinePlaceholder":30},[75,6490,6491,6494,6497,6500,6502,6505,6507,6510],{"class":77,"line":2259},[75,6492,6493],{"class":81},"export",[75,6495,6496],{"class":81}," function",[75,6498,6499],{"class":190}," encrypt",[75,6501,2135],{"class":85},[75,6503,6504],{"class":501},"data",[75,6506,1883],{"class":81},[75,6508,6509],{"class":171}," string",[75,6511,2273],{"class":85},[75,6513,6514,6516,6519,6521,6523,6526,6529,6531],{"class":77,"line":2276},[75,6515,2081],{"class":81},[75,6517,6518],{"class":171}," cipher",[75,6520,605],{"class":81},[75,6522,6474],{"class":85},[75,6524,6525],{"class":190},"createCipheriv",[75,6527,6528],{"class":85},"(algorithm, Buffer.",[75,6530,2599],{"class":190},[75,6532,6533],{"class":85},"(key), iv)\n",[75,6535,6536,6539,6542,6544,6547,6549,6552,6555,6557,6559],{"class":77,"line":2294},[75,6537,6538],{"class":81},"  let",[75,6540,6541],{"class":85}," encrypted ",[75,6543,89],{"class":81},[75,6545,6546],{"class":85}," cipher.",[75,6548,3358],{"class":190},[75,6550,6551],{"class":85},"(data, ",[75,6553,6554],{"class":92},"\"utf-8\"",[75,6556,1520],{"class":85},[75,6558,6437],{"class":92},[75,6560,585],{"class":85},[75,6562,6563,6566,6569,6571,6574,6576,6578],{"class":77,"line":2299},[75,6564,6565],{"class":85},"  encrypted ",[75,6567,6568],{"class":81},"+=",[75,6570,6546],{"class":85},[75,6572,6573],{"class":190},"final",[75,6575,2135],{"class":85},[75,6577,6437],{"class":92},[75,6579,585],{"class":85},[75,6581,6582],{"class":77,"line":2308},[75,6583,527],{"emptyLinePlaceholder":30},[75,6585,6586],{"class":77,"line":2792},[75,6587,6588],{"class":2542},"  // Package the IV and encrypted data together so it can be stored in a single\n",[75,6590,6591],{"class":77,"line":2802},[75,6592,6593],{"class":2542},"  // column in the database.\n",[75,6595,6596,6599,6602,6604,6606,6608,6610,6613],{"class":77,"line":2810},[75,6597,6598],{"class":81},"  return",[75,6600,6601],{"class":85}," iv.",[75,6603,3467],{"class":190},[75,6605,2135],{"class":85},[75,6607,6437],{"class":92},[75,6609,2070],{"class":85},[75,6611,6612],{"class":81},"+",[75,6614,6615],{"class":85}," encrypted\n",[75,6617,6618],{"class":77,"line":2815},[75,6619,325],{"class":85},[75,6621,6622],{"class":77,"line":3092},[75,6623,527],{"emptyLinePlaceholder":30},[75,6625,6626,6628,6630,6633,6635,6637,6639,6641],{"class":77,"line":3103},[75,6627,6493],{"class":81},[75,6629,6496],{"class":81},[75,6631,6632],{"class":190}," decrypt",[75,6634,2135],{"class":85},[75,6636,6504],{"class":501},[75,6638,1883],{"class":81},[75,6640,6509],{"class":171},[75,6642,2273],{"class":85},[75,6644,6645],{"class":77,"line":3114},[75,6646,6647],{"class":2542},"  // Unpackage the combined iv + encrypted message. Since we are using a fixed\n",[75,6649,6650],{"class":77,"line":3120},[75,6651,6652],{"class":2542},"  // size IV, we can hard code the slice length.\n",[75,6654,6655,6657,6660,6662,6665,6668,6670,6672,6674,6676],{"class":77,"line":3126},[75,6656,2081],{"class":81},[75,6658,6659],{"class":171}," inputIV",[75,6661,605],{"class":81},[75,6663,6664],{"class":85}," data.",[75,6666,6667],{"class":190},"slice",[75,6669,2135],{"class":85},[75,6671,6451],{"class":171},[75,6673,1520],{"class":85},[75,6675,6456],{"class":171},[75,6677,585],{"class":85},[75,6679,6680,6682,6684,6686,6688,6690,6692,6694],{"class":77,"line":3147},[75,6681,2081],{"class":81},[75,6683,4770],{"class":171},[75,6685,605],{"class":81},[75,6687,6664],{"class":85},[75,6689,6667],{"class":190},[75,6691,2135],{"class":85},[75,6693,6456],{"class":171},[75,6695,585],{"class":85},[75,6697,6698,6700,6703,6705,6707,6710],{"class":77,"line":5604},[75,6699,2081],{"class":81},[75,6701,6702],{"class":171}," decipher",[75,6704,605],{"class":81},[75,6706,6474],{"class":85},[75,6708,6709],{"class":190},"createDecipheriv",[75,6711,2197],{"class":85},[75,6713,6714],{"class":77,"line":5610},[75,6715,6716],{"class":85},"    algorithm,\n",[75,6718,6719,6722,6724],{"class":77,"line":5630},[75,6720,6721],{"class":85},"    Buffer.",[75,6723,2599],{"class":190},[75,6725,6726],{"class":85},"(key),\n",[75,6728,6729,6731,6733,6736,6738],{"class":77,"line":5645},[75,6730,6721],{"class":85},[75,6732,2599],{"class":190},[75,6734,6735],{"class":85},"(inputIV, ",[75,6737,6437],{"class":92},[75,6739,4630],{"class":85},[75,6741,6742],{"class":77,"line":5650},[75,6743,6744],{"class":85},"  )\n",[75,6746,6747],{"class":77,"line":5655},[75,6748,527],{"emptyLinePlaceholder":30},[75,6750,6751,6753,6756,6758,6761,6763,6766,6768,6770,6772],{"class":77,"line":5661},[75,6752,6538],{"class":81},[75,6754,6755],{"class":85}," decrypted ",[75,6757,89],{"class":81},[75,6759,6760],{"class":85}," decipher.",[75,6762,3358],{"class":190},[75,6764,6765],{"class":85},"(encrypted, ",[75,6767,6437],{"class":92},[75,6769,1520],{"class":85},[75,6771,6554],{"class":92},[75,6773,585],{"class":85},[75,6775,6776,6779,6781,6783,6785,6787,6789],{"class":77,"line":5682},[75,6777,6778],{"class":85},"  decrypted ",[75,6780,6568],{"class":81},[75,6782,6760],{"class":85},[75,6784,6573],{"class":190},[75,6786,2135],{"class":85},[75,6788,6554],{"class":92},[75,6790,585],{"class":85},[75,6792,6793,6795],{"class":77,"line":5687},[75,6794,6598],{"class":81},[75,6796,6797],{"class":85}," decrypted\n",[75,6799,6800],{"class":77,"line":5704},[75,6801,325],{"class":85},[17,6803,6804],{},"The nice thing about this implementation is that it returns a plain string with the IV and encrypted data sandwiched together. As long as you use the decrypt function, it knows how to properly decrypt the value making it really simple to store in a database.",[66,6806,6808],{"className":2039,"code":6807,"language":2041,"meta":15,"style":15},"const password = \"my secret password, don't tell anyone!\"\nconst encrypted = encrypt(password)\nconst decrypted = decrypt(encrypted)\n",[49,6809,6810,6822,6835],{"__ignoreMap":15},[75,6811,6812,6814,6817,6819],{"class":77,"line":78},[75,6813,2490],{"class":81},[75,6815,6816],{"class":171}," password",[75,6818,605],{"class":81},[75,6820,6821],{"class":92}," \"my secret password, don't tell anyone!\"\n",[75,6823,6824,6826,6828,6830,6832],{"class":77,"line":25},[75,6825,2490],{"class":81},[75,6827,4770],{"class":171},[75,6829,605],{"class":81},[75,6831,6499],{"class":190},[75,6833,6834],{"class":85},"(password)\n",[75,6836,6837,6839,6841,6843,6845],{"class":77,"line":215},[75,6838,2490],{"class":81},[75,6840,4842],{"class":171},[75,6842,605],{"class":81},[75,6844,6632],{"class":190},[75,6846,6847],{"class":85},"(encrypted)\n",[17,6849,6850],{},"I’ll probably also explore re-implementing this same logic using the web crypto API. But that’s for another day!",[1737,6852,6854],{"id":6853},"bottom-line","Bottom Line",[1742,6856,6857,6867,6874,6884,6897],{},[1745,6858,6859,2446,6861,6863,6864,6866],{},[49,6860,2445],{},[49,6862,2449],{},": Turn PEM/DER/JWK into safe, in-memory ",[49,6865,2404],{},"s.",[1745,6868,6869,2446,6871,6873],{},[49,6870,3240],{},[49,6872,3978],{},": Build “sign” or “verify” streams for signing data incrementally.",[1745,6875,6876,2446,6878,6880,6881,6883],{},[49,6877,4391],{},[49,6879,4394],{},": One-line wrappers when you just have a single ",[49,6882,2421],{},"/string payload.",[1745,6885,6886,6887,1520,6890,6893,6894,2030],{},"When using RSA, prefer PSS padding. When using EC, pick a modern curve (e.g. ",[49,6888,6889],{},"prime256v1",[49,6891,6892],{},"secp384r1",", or ",[49,6895,6896],{},"Ed25519",[1745,6898,6899,6900,6903],{},"Keep private keys encrypted at rest, load them via ",[49,6901,6902],{},"createPrivateKey({ passphrase })",", and never log raw PEM.",[17,6905,6906,6907,991,6909,140,6911,991,6913,6915,6916,6918,6919,6921],{},"With that, you should have a clear mental map of how Node’s ",[49,6908,2449],{},[49,6910,2445],{},[49,6912,3240],{},[49,6914,3978],{}," calls all fit together. If you need to do anything beyond signing (e.g. encryption, key exchange, HMAC), the rest of the ",[49,6917,2400],{}," module follows the same pattern: import or generate a ",[49,6920,2404],{},", pick an algorithm, feed in data, and call the appropriate one-line helper (or use a streamable object).",[1697,6923,6924],{},"html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":15,"searchDepth":25,"depth":25,"links":6926},[6927,6933,6937,6942,6944,6949,6953,6962,6963,6964,6965,6968],{"id":2396,"depth":25,"text":6928,"children":6929},"1. Key Concepts in crypto",[6930,6931,6932],{"id":2403,"depth":215,"text":2404},{"id":2458,"depth":215,"text":2459},{"id":2546,"depth":215,"text":2547},{"id":2575,"depth":25,"text":2576,"children":6934},[6935,6936],{"id":2579,"depth":215,"text":2580},{"id":2864,"depth":215,"text":2865},{"id":3236,"depth":25,"text":6938,"children":6939},"3. Signing Data (createSign)",[6940,6941],{"id":3265,"depth":215,"text":3266},{"id":3689,"depth":215,"text":3690},{"id":3974,"depth":25,"text":6943},"4. Verifying a Signature (createVerify)",{"id":4147,"depth":25,"text":6945,"children":6946},"5. createPrivateKey & createPublicKey in Detail",[6947,6948],{"id":4157,"depth":215,"text":4160},{"id":4290,"depth":215,"text":4293},{"id":4407,"depth":25,"text":4408,"children":6950},[6951,6952],{"id":4419,"depth":215,"text":4422},{"id":4573,"depth":215,"text":4576},{"id":4702,"depth":25,"text":4703,"children":6954},[6955,6957,6959,6960],{"id":4712,"depth":215,"text":6956},"crypto.publicEncrypt(publicKey, buffer) / crypto.privateDecrypt(privateKey, buffer)",{"id":4906,"depth":215,"text":6958},"crypto.privateEncrypt(privateKey, buffer) / crypto.publicDecrypt(publicKey, buffer)",{"id":4929,"depth":215,"text":4932},{"id":5142,"depth":215,"text":6961},"crypto.generateKeyPair() and crypto.generateKeyPairSync()",{"id":5281,"depth":25,"text":5282},{"id":5783,"depth":25,"text":5784},{"id":5916,"depth":25,"text":5917},{"id":6094,"depth":25,"text":6095,"children":6966},[6967],{"id":6292,"depth":215,"text":6293},{"id":6853,"depth":25,"text":6854},"backend","Key Concepts in nodejs crypto","/knowledge-base/nodejs-crypto.png",{},"/knowledge-base/crypto","15",{"title":2391,"description":6970},{"loc":6973},"knowledge-base/crypto",[6979,6980,2386,6981],"Node.js","Crypto","Cryptography","e-Pcom4XcyKqkXmiL83E-Xw0D4u7lB5iiNT6hBRCeA4",{"id":6984,"title":6985,"body":6986,"category":7926,"contentType":2374,"date":1712,"description":7927,"extension":28,"head":27,"image":7928,"meta":7929,"navigation":30,"ogImage":27,"path":7930,"readingTime":1717,"robots":27,"schemaOrg":27,"seo":7931,"sitemap":7932,"stem":7933,"tags":7934,"__hash__":7936},"knowledgeBase/knowledge-base/iptables.md","Introduction to iptables",{"type":7,"value":6987,"toc":7914},[6988,6991,6998,7002,7005,7023,7029,7042,7046,7049,7078,7081,7116,7134,7138,7149,7209,7216,7319,7333,7337,7343,7347,7461,7465,7476,7521,7527,7608,7611,7615,7621,7635,7667,7676,7679,7688,7692,7695,7855,7859,7862,7905,7908,7911],[17,6989,6990],{},"iptables is a powerful firewall tool built into the Linux kernel's networking stack. It allows administrators to define rules that control how network packets are handled (filtered, modified, or forwarded) by the system. Under the hood, iptables works with the Linux netfilter framework -- a series of hooks in the kernel that inspect packets at various points. By adding iptables rules, you're instructing the kernel on what to do with packets (e.g., accept, drop, modify) based on specified criteria. This makes iptables central to packet filtering (firewalling) and Network Address Translation (NAT) on Linux systems.",[17,6992,6993,6994,6997],{},"Netfilter and Stateful Filtering: iptables operates as part of netfilter, meaning it can track connections (stateful inspection). Packets aren't treated in isolation; the kernel connection tracking system notes which packets belong to an established session. This allows iptables to have rules like \"allow established connections\" rather than having to allow every response packet explicitly. (For example, a rule can match ",[49,6995,6996],{},"ESTABLISHED"," state to permit reply traffic.) By default, iptables has no rules until configured -- all behavior is defined by user-set rules and policies. Many Linux distributions ship with a default iptables policy (often via tools like UFW or firewalld) that initially accepts or drops traffic according to security best practices (commonly default deny incoming, allow outgoing, etc.).",[1737,6999,7001],{"id":7000},"iptables-tables-filter-nat-mangle-and-raw","iptables Tables: filter, nat, mangle, and raw",[17,7003,7004],{},"iptables organizes rules into tables, each for a different purpose. The main tables you'll encounter are:",[1742,7006,7007,7010,7013,7016],{},[1745,7008,7009],{},"filter table: The default table for firewall rules. It's used for making filtering decisions -- i.e., whether to allow or block packets. Most \"classic\" firewall rules (allowing SSH, blocking an IP, etc.) reside in the filter table. If a rule is about permitting or denying traffic, it goes here.",[1745,7011,7012],{},"nat table: This table handles Network Address Translation rules, which modify packet source or destination addresses. For instance, port forwarding (destination NAT) and Masquerading (a form of source NAT) are implemented in the nat table. Rules here usually apply only to the first packet of a new connection (connection tracking ensures subsequent packets use the same NAT decision).",[1745,7014,7015],{},"mangle table: Used for specialized packet alterations. The mangle table can modify packet headers -- e.g., changing the TTL (Time To Live) or TOS/DSCP fields for QoS, or marking packets internally. It's not for filtering or NAT, but for adjustments that affect how packets are treated. For example, mangle can attach a mark to a packet (an internal tag in the kernel) for use by other subsystems (such as routing or traffic shaping).",[1745,7017,7018,7019,7022],{},"raw table: A special table consulted very early in packet processing, used mainly to exempt packets from connection tracking. The raw table's primary (and very narrow) function is to mark packets with a ",[49,7020,7021],{},"NOTRACK"," target if you want certain traffic to bypass the state tracking system. This table is rarely used unless you have specific performance or tracking-exclusion needs.",[7024,7025,7026],"note",{},[17,7027,7028],{},"There is also a security table used to apply SELinux security context marks on packets. It's only relevant on systems with Mandatory Access Control policies like SELinux.",[17,7030,7031,7032,7035,7036,7038,7039,7041],{},"Each table contains chains where rules are actually placed. Some tables share chain names, but the chains in each table are independent (for example, both the filter and mangle tables have an ",[49,7033,7034],{},"INPUT"," chain, but you might use the filter ",[49,7037,7034],{}," to decide on packet acceptance and the mangle ",[49,7040,7034],{}," to modify packet bits).",[1737,7043,7045],{"id":7044},"chains-and-packet-flow-in-iptables","Chains and Packet Flow in iptables",[17,7047,7048],{},"Chains represent stages of packet processing. iptables has five built-in chain names which correspond to key routing points in the netfilter architecture:",[1742,7050,7051,7058,7065,7072,7075],{},[1745,7052,7053,7054,7057],{},"PREROUTING: Applies to incoming packets before the system makes a routing decision (i.e., before it decides if the packet is destined for the local machine or should be forwarded onward). This is typically used for DNAT (destination NAT) -- e.g., port forwarding -- since you might need to alter the packet's destination ",[1795,7055,7056],{},"before"," routing. The nat and mangle tables have PREROUTING chains (raw does as well), allowing NAT and header tweaks on inbound packets immediately as they arrive.",[1745,7059,7060,7061,7064],{},"INPUT: Applies to packets ",[1795,7062,7063],{},"destined for the local system"," (after routing has determined the packet is for us). The INPUT chain in the filter table is where you decide to accept or drop incoming traffic to the host itself. Mangle and security tables also have INPUT chains (for modifying or labeling packets heading to local sockets). If a packet is addressed to this machine, it will hit PREROUTING first, then go to INPUT (after some other processing).",[1745,7066,7067,7068,7071],{},"FORWARD: Applies to packets that are routed ",[1795,7069,7070],{},"through"," your machine (not local delivery). If your Linux box is acting as a router or gateway, packets not aimed at it will hit the FORWARD chain. The filter table's FORWARD chain is where you permit or block transit traffic. (Mangle and security also have FORWARD chains for modifications.) Inbound packets trigger PREROUTING, then hit FORWARD (if not local), then POSTROUTING on the way out.",[1745,7073,7074],{},"OUTPUT: Applies to packets originating from the local system (i.e. locally generated by an application on your server). Before such a packet leaves, it traverses the OUTPUT chains. For example, the filter table's OUTPUT chain could filter outgoing traffic; the nat table's OUTPUT chain could do DNAT for locally generated connections (rare, but for scenarios like redirecting local traffic to a proxy); the mangle OUTPUT can mark or modify local out packets. A packet created locally goes through OUTPUT then POSTROUTING before hitting the wire.",[1745,7076,7077],{},"POSTROUTING: Applies after routing is decided, just before the packet leaves the system. POSTROUTING is commonly used for SNAT (source NAT) such as MASQUERADE, because once we know the outgoing interface and have made routing decisions, we can alter the source address if needed. The nat table's POSTROUTING chain is where you put MASQUERADE/SNAT rules. The mangle table's POSTROUTING can also adjust packets (e.g., tweak QoS bits) right as they exit.",[17,7079,7080],{},"These chains form the path a packet takes. In summary:",[1742,7082,7083,7093,7107],{},[1745,7084,7085,7086,7089,7090,7092],{},"Incoming to local host: ",[49,7087,7088],{},"PREROUTING"," (nat/mangle) -> routing decision -> ",[49,7091,7034],{}," (filter, etc.) -> local process.",[1745,7094,7095,7096,7098,7099,7102,7103,7106],{},"Incoming to be forwarded: ",[49,7097,7088],{}," -> routing decision -> ",[49,7100,7101],{},"FORWARD"," -> ",[49,7104,7105],{},"POSTROUTING"," -> out to next hop.",[1745,7108,7109,7110,7098,7113,7115],{},"Outgoing from local host: ",[49,7111,7112],{},"OUTPUT",[49,7114,7105],{}," -> out to network.",[17,7117,7118,7119,7122,7123,52,7126,7129,7130,7133],{},"Within each chain, rules are evaluated in order (top to bottom). When a packet matches a rule, a corresponding target action is taken. Some targets (like DROP or ACCEPT) are ",[1795,7120,7121],{},"terminating"," --- meaning no further rules in that chain are checked once they hit --- while others (like ",[49,7124,7125],{},"LOG",[49,7127,7128],{},"MARK",") are non-terminating and evaluation will continue to the next rule. If a packet reaches the end of a built-in chain without matching any rule, the chain's policy (default action) is applied (which could be ACCEPT or DROP for the chain). You can also create user-defined chains and use a ",[49,7131,7132],{},"-j \u003Cchain>"," jump target to send packets to those for better organization, but the built-in ones above are where packets enter from the kernel's perspective.",[1737,7135,7137],{"id":7136},"iptables-rule-syntax-and-structure","iptables Rule Syntax and Structure",[17,7139,7140,7141,7144,7145,7148],{},"You interact with iptables via the ",[49,7142,7143],{},"iptables"," command-line utility (for IPv4) and ",[49,7146,7147],{},"ip6tables"," (for IPv6) -- though many distros now use a variant that handles both, or use nftables in compatibility mode (more on that later). The basic syntax of an iptables rule is:",[66,7150,7152],{"className":68,"code":7151,"language":71,"meta":15,"style":15},"iptables [-t \u003Ctable>] -A \u003Cchain> [match criteria] -j \u003Ctarget>\n",[49,7153,7154],{"__ignoreMap":15},[75,7155,7156,7158,7161,7164,7167,7169,7172,7175,7178,7181,7184,7187,7189,7192,7195,7198,7200,7203,7206],{"class":77,"line":78},[75,7157,7143],{"class":190},[75,7159,7160],{"class":85}," [-t ",[75,7162,7163],{"class":81},"\u003C",[75,7165,7166],{"class":92},"tabl",[75,7168,1164],{"class":85},[75,7170,7171],{"class":81},">",[75,7173,7174],{"class":92},"]",[75,7176,7177],{"class":171}," -A",[75,7179,7180],{"class":81}," \u003C",[75,7182,7183],{"class":92},"chai",[75,7185,7186],{"class":85},"n",[75,7188,7171],{"class":81},[75,7190,7191],{"class":85}," [match ",[75,7193,7194],{"class":92},"criteria]",[75,7196,7197],{"class":171}," -j",[75,7199,7180],{"class":81},[75,7201,7202],{"class":92},"targe",[75,7204,7205],{"class":85},"t",[75,7207,7208],{"class":81},">\n",[17,7210,7211,7212,7215],{},"For example, ",[49,7213,7214],{},"iptables -A INPUT -p tcp --dport 22 -j ACCEPT"," means \"Append to the INPUT chain a rule that matches TCP packets destined to port 22, and Jump to target ACCEPT (allow it)\". Key components of rules:",[1742,7217,7218,7250,7312],{},[1745,7219,7220,7221,6358,7223,7226,7227,7230,7231,7233,7234,7237,7238,7241,7242,7245,7246,7249],{},"Table and Chain: You specify which ",[1795,7222,1464],{},[49,7224,7225],{},"-t",") and ",[1795,7228,7229],{},"chain"," to operate on. If ",[49,7232,7225],{}," is omitted, it defaults to the filter table. ",[49,7235,7236],{},"-A"," (append) adds the rule to the end of the given chain. You can also use ",[49,7239,7240],{},"-I"," to insert at a certain position, ",[49,7243,7244],{},"-D"," to delete, ",[49,7247,7248],{},"-P"," to set chain policy, etc.",[1745,7251,7252,7253],{},"Match criteria: These are conditions a packet must meet to trigger the rule. Common matches include:",[1742,7254,7255,7268,7277,7287,7298,7305],{},[1745,7256,7257,7258,1520,7261,1520,7264,7267],{},"Protocol (",[49,7259,7260],{},"-p tcp",[49,7262,7263],{},"-p udp",[49,7265,7266],{},"-p icmp",", etc.).",[1745,7269,7270,7271,52,7274,2030],{},"Source or destination IP (",[49,7272,7273],{},"-s 203.0.113.5",[49,7275,7276],{},"-d 10.1.1.0/24",[1745,7278,7279,7280,52,7283,7286],{},"Source or destination port (",[49,7281,7282],{},"--dport 80",[49,7284,7285],{},"--sport 53"," for UDP/TCP).",[1745,7288,7289,7290,7293,7294,7297],{},"Interface (",[49,7291,7292],{},"-i eth0"," for incoming on eth0, or ",[49,7295,7296],{},"-o wg0"," for outgoing interface).",[1745,7299,7300,7301,7304],{},"Connection state (",[49,7302,7303],{},"-m conntrack --ctstate NEW,ESTABLISHED"," to match packets that are starting a new connection or part of an existing one, for instance).",[1745,7306,7307,7308,7311],{},"Many others: You can match on TCP flags, ICMP types, packet markings, etc., using extension modules (",[49,7309,7310],{},"-m \u003Cmodule>","). The matching system is very flexible.",[1745,7313,7314,7315,7318],{},"Target (action): The ",[49,7316,7317],{},"-j \u003Ctarget>"," specifies what to do if the packet matches. Built-in targets include ACCEPT (let the packet through), DROP (silently discard it), REJECT (discard and send an ICMP error back), LOG (log the packet details and continue), MASQUERADE, DNAT, SNAT (for NAT in the nat table), MARK (mark the packet in mangle), RETURN (stop processing in a user-defined chain and go back), etc. The target can also be a jump to a user-defined chain.",[17,7320,7321,7322,7324,7325,7328,7329,7332],{},"Stateful rules example: A common pattern is to use connection tracking to simplify rules. For instance, in the filter ",[49,7323,7034],{}," chain, one might allow established traffic with a rule like ",[49,7326,7327],{},"-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"," (meaning any packet that is part of an existing connection or related to one is accepted). Then you only specifically allow new inbound connections on certain ports (e.g., ",[49,7330,7331],{},"-A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT"," for SSH, etc.), and drop everything else by default. This way, return traffic from your machine (like replies to outbound requests) is automatically permitted because it's ESTABLISHED, even if your default policy is to drop unsolicited incoming packets.",[1737,7334,7336],{"id":7335},"practical-examples-with-iptables","Practical Examples with iptables",[17,7338,7339,7340,7342],{},"Let's go through some common use-cases and how you'd implement them with iptables rules. These examples assume we're manipulating IPv4 rules (using the ",[49,7341,7143],{}," command) and that default policies are set to a secure baseline (e.g., default DROP for incoming traffic, etc.), though the concepts apply generally.",[58,7344,7346],{"id":7345},"basic-packet-filtering-allowingblocking-traffic","Basic Packet Filtering (Allowing/Blocking Traffic)",[1742,7348,7349,7410,7443],{},[1745,7350,7351,7352,7354,7355,7407,7409],{},"Allowing specific inbound ports: Suppose you want to allow incoming HTTP and SSH traffic to your server. In iptables filter table, that means adding rules to the ",[49,7353,7034],{}," chain for those ports. For example, to allow TCP port 80 (HTTP) and 22 (SSH) you would run:",[66,7356,7358],{"className":68,"code":7357,"language":71,"meta":15,"style":15},"iptables -A INPUT -p tcp --dport 80 -j ACCEPT\niptables -A INPUT -p tcp --dport 22 -j ACCEPT\n",[49,7359,7360,7386],{"__ignoreMap":15},[75,7361,7362,7364,7366,7369,7372,7375,7378,7381,7383],{"class":77,"line":78},[75,7363,7143],{"class":190},[75,7365,7177],{"class":171},[75,7367,7368],{"class":92}," INPUT",[75,7370,7371],{"class":171}," -p",[75,7373,7374],{"class":92}," tcp",[75,7376,7377],{"class":171}," --dport",[75,7379,7380],{"class":171}," 80",[75,7382,7197],{"class":171},[75,7384,7385],{"class":92}," ACCEPT\n",[75,7387,7388,7390,7392,7394,7396,7398,7400,7403,7405],{"class":77,"line":25},[75,7389,7143],{"class":190},[75,7391,7177],{"class":171},[75,7393,7368],{"class":92},[75,7395,7371],{"class":171},[75,7397,7374],{"class":92},[75,7399,7377],{"class":171},[75,7401,7402],{"class":171}," 22",[75,7404,7197],{"class":171},[75,7406,7385],{"class":92},[6038,7408],{},"This appends rules permitting TCP packets destined for port 80 and 22 to be accepted. (You would likely also allow port 443 for HTTPS, etc., in a similar way.)",[1745,7411,7412,7413,7436,7438,7439,7442],{},"Blocking a specific IP address: If there's a malicious host (say 203.0.113.45) that you want to block from even reaching your server, you can add a DROP rule targeting that source. For instance:",[66,7414,7416],{"className":68,"code":7415,"language":71,"meta":15,"style":15},"iptables -A INPUT -s 203.0.113.45 -j DROP\n",[49,7417,7418],{"__ignoreMap":15},[75,7419,7420,7422,7424,7426,7428,7431,7433],{"class":77,"line":78},[75,7421,7143],{"class":190},[75,7423,7177],{"class":171},[75,7425,7368],{"class":92},[75,7427,842],{"class":171},[75,7429,7430],{"class":171}," 203.0.113.45",[75,7432,7197],{"class":171},[75,7434,7435],{"class":92}," DROP\n",[6038,7437],{},"This means any packet with source IP 203.0.113.45 arriving at your server is immediately dropped. Typically, such a rule is placed near the top of the chain (using ",[49,7440,7441],{},"-I INPUT 1"," to insert at the top) so it takes effect before other rules (ensuring that IP is blocked unconditionally).",[1745,7444,7445,7446,7448,7449,7452,7453,7456,7457,7460],{},"Dropping all other unsolicited traffic: In a simple firewall, after allowing a few safe services, you'd usually drop the rest. You can either set the default policy of ",[49,7447,7034],{}," to DROP (",[49,7450,7451],{},"iptables -P INPUT DROP",") and then whitelist ports, or place a broad rule like ",[49,7454,7455],{},"iptables -A INPUT -j DROP"," at the end of the chain. The policy method is often cleaner. Don't forget to allow essential traffic (like allowing loopback interface traffic ",[49,7458,7459],{},"-A INPUT -i lo -j ACCEPT",", and perhaps pings or DNS replies as needed) and, as noted, use connection tracking to allow replies (ESTABLISHED) without needing explicit rules for them.",[58,7462,7464],{"id":7463},"port-forwarding-dnat-example","Port Forwarding (DNAT Example)",[17,7466,7467,7468,7471,7472,7475],{},"Port forwarding is when your firewall/gateway machine listens on a port and forwards those packets to an internal host. This is done with Destination NAT (DNAT) in the ",[49,7469,7470],{},"nat"," table's PREROUTING chain. For example, imagine you have a gateway with public IP and an internal webserver at ",[49,7473,7474],{},"10.0.0.1"," on a private network. To forward port 80 from the gateway to the internal webserver, you could use:",[66,7477,7479],{"className":68,"code":7478,"language":71,"meta":15,"style":15},"iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1:80\n",[49,7480,7481],{"__ignoreMap":15},[75,7482,7483,7485,7488,7491,7493,7496,7499,7502,7504,7506,7508,7510,7512,7515,7518],{"class":77,"line":78},[75,7484,7143],{"class":190},[75,7486,7487],{"class":171}," -t",[75,7489,7490],{"class":92}," nat",[75,7492,7177],{"class":171},[75,7494,7495],{"class":92}," PREROUTING",[75,7497,7498],{"class":171}," -i",[75,7500,7501],{"class":92}," eth0",[75,7503,7371],{"class":171},[75,7505,7374],{"class":92},[75,7507,7377],{"class":171},[75,7509,7380],{"class":171},[75,7511,7197],{"class":171},[75,7513,7514],{"class":92}," DNAT",[75,7516,7517],{"class":171}," --to-destination",[75,7519,7520],{"class":92}," 10.0.0.1:80\n",[17,7522,7523,7524,7526],{},"This means: for packets arriving on interface eth0 (assumed to be the public interface) with TCP destination port 80, rewrite the destination IP to ",[49,7525,7474],{}," (and port 80). Now the packet will be sent to the internal host. However, the internal host's replies need to get back to the original client. Two additional things are needed for this setup to work properly:",[3245,7528,7529,7547],{},[1745,7530,7531,7532,7535,7536,7539,7540,7543,7544,2030],{},"IP Forwarding enabled: The Linux kernel must be allowed to forward packets. Ensure ",[49,7533,7534],{},"/proc/sys/net/ipv4/ip_forward"," is ",[49,7537,7538],{},"1"," (you can set this in ",[49,7541,7542],{},"/etc/sysctl.conf"," and apply with ",[49,7545,7546],{},"sysctl -p",[1745,7548,7549,7550,7552,7553,7590,7592,7593,7595,7596,7599,7600,7603,7604,7607],{},"Source NAT for replies: If the internal webserver ",[49,7551,7474],{}," tries to reply directly to the client on the internet, the packets might get dropped (often, private IP to public internet will be blocked by ISP or won't route properly). Also, the client would see a reply from an IP it didn't send to. To fix this, you SNAT the outgoing packet so that replies go back through the gateway. For instance:",[66,7554,7556],{"className":68,"code":7555,"language":71,"meta":15,"style":15},"iptables -t nat -A POSTROUTING -p tcp -d 10.0.0.1 --dport 80 -j MASQUERADE\n",[49,7557,7558],{"__ignoreMap":15},[75,7559,7560,7562,7564,7566,7568,7571,7573,7575,7578,7581,7583,7585,7587],{"class":77,"line":78},[75,7561,7143],{"class":190},[75,7563,7487],{"class":171},[75,7565,7490],{"class":92},[75,7567,7177],{"class":171},[75,7569,7570],{"class":92}," POSTROUTING",[75,7572,7371],{"class":171},[75,7574,7374],{"class":92},[75,7576,7577],{"class":171}," -d",[75,7579,7580],{"class":171}," 10.0.0.1",[75,7582,7377],{"class":171},[75,7584,7380],{"class":171},[75,7586,7197],{"class":171},[75,7588,7589],{"class":92}," MASQUERADE\n",[6038,7591],{},"This rule (in nat ",[49,7594,7105],{},") will masquerade the source of the forwarded packets, making them appear from the gateway's IP. In effect, the internal server's replies will go to the gateway, which will then DNAT them back to the client. (",[49,7597,7598],{},"MASQUERADE"," is a variant of SNAT that automatically uses the outgoing interface's address -- more on it below.) The combination of DNAT in PREROUTING and SNAT/MASQUERADE in POSTROUTING ensures the port forward works both ways. Don't forget to also allow the forwarded traffic in the ",[49,7601,7602],{},"filter"," table's FORWARD chain (e.g., ",[49,7605,7606],{},"-A FORWARD -p tcp -d 10.0.0.1 --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT",", plus a rule for the ESTABLISHED replies) if your default FORWARD policy is DROP.",[17,7609,7610],{},"After these rules, anyone hitting port 80 on the gateway's public IP will actually reach the internal host. This is how you \"expose\" a service from an internal network to the outside using iptables.",[58,7612,7614],{"id":7613},"source-nat-masquerade-for-outgoing-traffic","Source NAT (MASQUERADE) for Outgoing Traffic",[17,7616,7617,7618,7620],{},"The nat POSTROUTING chain is typically used for outbound NAT. A common scenario is a Linux machine acting as a router for a private LAN out to the internet. The LAN hosts have private IPs (e.g., 192.168.1.x) which aren't routable on the internet, so the router uses NAT to masquerade their traffic behind its public IP. This is done with the ",[49,7619,7598],{}," target on the router's external interface.",[17,7622,7623,7624,7627,7628,7631,7632,7634],{},"For example, to enable NAT for a LAN ",[49,7625,7626],{},"192.168.1.0/24"," going out via interface ",[49,7629,7630],{},"eth1"," (suppose ",[49,7633,7630],{}," is the uplink):",[66,7636,7638],{"className":68,"code":7637,"language":71,"meta":15,"style":15},"iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j MASQUERADE\n",[49,7639,7640],{"__ignoreMap":15},[75,7641,7642,7644,7646,7648,7650,7652,7654,7657,7660,7663,7665],{"class":77,"line":78},[75,7643,7143],{"class":190},[75,7645,7487],{"class":171},[75,7647,7490],{"class":92},[75,7649,7177],{"class":171},[75,7651,7570],{"class":92},[75,7653,842],{"class":171},[75,7655,7656],{"class":92}," 192.168.1.0/24",[75,7658,7659],{"class":171}," -o",[75,7661,7662],{"class":92}," eth1",[75,7664,7197],{"class":171},[75,7666,7589],{"class":92},[17,7668,7669,7670,7672,7673,7675],{},"This rule says: for packets coming from source network 192.168.1.0/24 and leaving out ",[49,7671,7630],{},", perform masquerading (i.e., replace their source IP with the router's ",[49,7674,7630],{}," IP). The MASQUERADE target is basically a dynamic form of SNAT -- it's ideal if your external IP may change (like a DHCP client) because it always uses the current interface address. The result is that to external servers, all traffic from the LAN appears to come from the router's single public IP. Multiple devices share one IP this way, and return traffic automatically gets un-NATed back to the correct LAN host. This is the essence of IP masquerading, allowing many clients to share a single public address for internet access.",[17,7677,7678],{},"Key points when using MASQUERADE/SNAT: enable IP forwarding (as mentioned) and usually set appropriate firewall rules in FORWARD chain to restrict what traffic is allowed out or in. Also note that MASQUERADE is usually only needed on the router/gateway device; clients on the LAN don't need to run iptables for this purpose.",[1852,7680,7681],{},[17,7682,7683,7684,7687],{},"You could also use a static SNAT rule like ",[49,7685,7686],{},"-j SNAT --to-source \u003CIP>"," if you know the exact public IP and it's static. MASQUERADE is simpler for most cases and automatically handles picking the IP. In iptables, MASQUERADE is actually a special case of SNAT.",[58,7689,7691],{"id":7690},"marking-packets-and-altering-headers-advanced-mangle-usage","Marking Packets and Altering Headers (Advanced mangle usage)",[17,7693,7694],{},"The mangle table provides capabilities to modify packet headers or mark packets for special handling:",[1742,7696,7697,7754,7800,7844],{},[1745,7698,7699,7700,991,7703,7706,7707,7744,7746,7747,7750,7751,7753],{},"Packet Marking: Using the MARK target, you can assign an internal mark to packets. This mark isn't part of the packet that goes over the network; it's a tag stored in kernel space alongside the packet. Marks are often used in advanced setups like policy routing (with ",[49,7701,7702],{},"ip rule",[49,7704,7705],{},"ip route"," directives that route packets differently based on mark) or Quality of Service (traffic shaping) where you want to treat certain traffic uniquely. For example, you might mark all packets going to a certain IP or port. A sample rule:",[66,7708,7710],{"className":68,"code":7709,"language":71,"meta":15,"style":15},"iptables -t mangle -A PREROUTING -p tcp --dport 22 -j MARK --set-mark 2\n",[49,7711,7712],{"__ignoreMap":15},[75,7713,7714,7716,7718,7721,7723,7725,7727,7729,7731,7733,7735,7738,7741],{"class":77,"line":78},[75,7715,7143],{"class":190},[75,7717,7487],{"class":171},[75,7719,7720],{"class":92}," mangle",[75,7722,7177],{"class":171},[75,7724,7495],{"class":92},[75,7726,7371],{"class":171},[75,7728,7374],{"class":92},[75,7730,7377],{"class":171},[75,7732,7402],{"class":171},[75,7734,7197],{"class":171},[75,7736,7737],{"class":92}," MARK",[75,7739,7740],{"class":171}," --set-mark",[75,7742,7743],{"class":171}," 2\n",[6038,7745],{},"This marks all TCP packets headed to port 22 (SSH) with a mark value of ",[49,7748,7749],{},"2",". By itself, marking does nothing visible, but another tool or later rule can use this mark. For instance, you could have a traffic shaper give priority to packets with mark 2, or use ",[49,7752,7702],{}," to route those through a VPN. The mangle table's PREROUTING (or OUTPUT, etc., depending on traffic direction) is where you'd place such rules. Remember, MARK does not alter the packet's content -- it's internal to the kernel's bookkeeping.",[1745,7755,7756,7757,7789,7791,7792,7795,7796,7799],{},"Changing TOS/DSCP or TTL: Mangle allows modifying certain header fields. For example, the TTL target can reduce or set the Time To Live of a packet. Why would you do this? A classical use-case: some ISPs detected multiple devices behind one connection by noticing different TTL values coming out (since each OS might use different initial TTLs). By forcing all outgoing packets to a fixed TTL, you could obscure that. For instance:",[66,7758,7760],{"className":68,"code":7759,"language":71,"meta":15,"style":15},"iptables -t mangle -A POSTROUTING -o eth0 -j TTL --ttl-set 64\n",[49,7761,7762],{"__ignoreMap":15},[75,7763,7764,7766,7768,7770,7772,7774,7776,7778,7780,7783,7786],{"class":77,"line":78},[75,7765,7143],{"class":190},[75,7767,7487],{"class":171},[75,7769,7720],{"class":92},[75,7771,7177],{"class":171},[75,7773,7570],{"class":92},[75,7775,7659],{"class":171},[75,7777,7501],{"class":92},[75,7779,7197],{"class":171},[75,7781,7782],{"class":92}," TTL",[75,7784,7785],{"class":171}," --ttl-set",[75,7787,7788],{"class":171}," 64\n",[6038,7790],{},"This would set the TTL of all packets leaving on eth0 to 64. Another example is changing TOS/DSCP bits (Type of Service/DiffServ Code Point) to mark traffic as low-latency or bulk. For instance, using ",[49,7793,7794],{},"-j TOS --set-tos 0x10"," (old TOS) or ",[49,7797,7798],{},"-j DSCP --set-dscp CS1"," to mark packets for QoS in your network. These modifications can influence routing or handling by other devices that respect those fields.",[1745,7801,7802,7803,7841,7843],{},"Example -- Throttling bulk traffic via DSCP: You could mark, say, all outgoing FTP traffic with a DSCP value indicating \"lower priority\". E.g.:",[66,7804,7806],{"className":68,"code":7805,"language":71,"meta":15,"style":15},"iptables -t mangle -A OUTPUT -p tcp --dport 21 -j DSCP --set-dscp 0x08\n",[49,7807,7808],{"__ignoreMap":15},[75,7809,7810,7812,7814,7816,7818,7821,7823,7825,7827,7830,7832,7835,7838],{"class":77,"line":78},[75,7811,7143],{"class":190},[75,7813,7487],{"class":171},[75,7815,7720],{"class":92},[75,7817,7177],{"class":171},[75,7819,7820],{"class":92}," OUTPUT",[75,7822,7371],{"class":171},[75,7824,7374],{"class":92},[75,7826,7377],{"class":171},[75,7828,7829],{"class":171}," 21",[75,7831,7197],{"class":171},[75,7833,7834],{"class":92}," DSCP",[75,7836,7837],{"class":171}," --set-dscp",[75,7839,7840],{"class":171}," 0x08\n",[6038,7842],{},"This sets the DSCP field to \"Low Priority Data\" for outbound FTP control packets (just an example). Your router or ISP could then de-prioritize such traffic.",[1745,7845,7846,7847,7850,7851,7854],{},"Other mangle uses: There are targets like ",[49,7848,7849],{},"TCPMSS"," (often used in mangle FORWARD chain) to adjust the TCP Maximum Segment Size of packets, which can prevent issues with Path MTU discovery in cases of misconfigured networks. For example, ",[49,7852,7853],{},"-A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360"," can clamp the MSS to a lower value to avoid fragmentation issues on VPNs or PPPoE links. In short, mangle is a toolbox for any packet modifications or markings that aren't simply \"filter or NAT\".",[1737,7856,7858],{"id":7857},"comparing-iptables-with-ufw-nftables-and-firewalld","Comparing iptables with UFW, nftables, and firewalld",[17,7860,7861],{},"Linux has multiple tools and layers for firewall management. iptables is low-level and powerful, but other systems exist to simplify or replace it. Here's how iptables contrasts with some commonly used alternatives:",[1742,7863,7864,7875,7885],{},[1745,7865,7866,7867,7870,7871,7874],{},"UFW (Uncomplicated Firewall): UFW is essentially a ",[1795,7868,7869],{},"front-end for iptables",". It provides a simplified syntax for common tasks, so users don't have to write raw iptables commands. For example, ",[49,7872,7873],{},"ufw allow 22/tcp"," will add the appropriate iptables rules to allow SSH. UFW was developed with Ubuntu in mind, aiming to make firewall configuration easy for beginners. Under the hood, on traditional systems UFW just manipulates iptables; on newer Ubuntu systems using nftables, UFW can output nftables rules, but the user doesn't see that. The main point is that UFW does not replace iptables's functionality -- it leverages it. The trade-off is simplicity vs. granularity: UFW handles basic needs with simple commands (including some nice features like application profiles), but for complex scenarios (like marking packets, custom chains, advanced NAT), you'd revert to raw iptables. In summary, UFW is great for quick setups and for those unfamiliar with iptables syntax, but it's essentially iptables under the hood.",[1745,7876,7877,7878,7881,7882,7884],{},"nftables: nftables is the modern successor to iptables. Introduced in Linux 3.13 (around 2014) and increasingly the default on many distributions, nftables replaces the iptables CLI and the underlying packet filtering engine with a more unified and efficient system. Key differences: nftables uses a single subsystem for IPv4/IPv6/Bridge/etc (no separate iptables/ip6tables binaries) and a new syntax that is more concise. It also can use sophisticated data structures (like sets and maps) in the kernel for efficient rule handling, meaning large rule sets perform better than in iptables (where rules are checked sequentially). In nftables, instead of fixed tables and chains, you can create your own tables and chains as needed (though commonly one uses similar concepts like an ",[49,7879,7880],{},"inet filter"," table that covers both v4 and v6). Many distributions have transitioned to using nftables by default, sometimes with an iptables compatibility layer (so the ",[49,7883,7143],{}," command you run is actually translating to nft rules in the background). If you're starting fresh or need performance with lots of rules, nftables is generally recommended as it's the future of Linux firewalls. However, iptables is still widely used and valid; both systems can coexist (though you should avoid using them at exactly the same time on the same machine to prevent conflicts).",[1745,7886,7887,7888,1520,7891,1520,7894,1520,7897,7900,7901,7904],{},"firewalld: firewalld is a higher-level firewall management service that typically uses nftables or iptables under the hood depending on the system. It is the default on Red Hat and related distros. The core idea of firewalld is a dynamic, zone-based firewall: it abstracts firewall rules into \"zones\" (like ",[1795,7889,7890],{},"public",[1795,7892,7893],{},"home",[1795,7895,7896],{},"internal",[1795,7898,7899],{},"dmz",", etc.), each with a certain trust level and set of allowed services. Network interfaces get assigned to zones. This is conceptually easier for some users -- e.g., you might say \"eth0 is in the public zone, only allow SSH and HTTP\" without manually writing port rules each time. Firewalld runs as a daemon and can apply rule changes live without dropping existing connections (iptables by itself can't easily do that without careful scripting). It provides a D-Bus API and CLI (",[49,7902,7903],{},"firewall-cmd",") for management. In practice, firewalld on modern systems leverages nftables. If someone enables firewalld, they should not manually use iptables at the same time (firewalld will overwrite or conflict with direct iptables changes unless put into a direct rule). The advantage of firewalld is easier management of complex rule sets (especially when using zones or rich rules) and integration with services (many apps ship firewalld service definitions). The disadvantage is an extra layer of abstraction -- for very custom setups, some find it cumbersome and prefer raw iptables/nftables. But for most, firewalld provides a good balance between simplicity and flexibility (more so than UFW, it's more enterprise-featured, allowing both simple service-based rules and complex rich rules).",[17,7906,7907],{},"In essence, iptables vs UFW vs firewalld comes down to direct control versus user-friendliness: iptables (and nftables) give you fine-grained control and are ideal for experienced users or complex policies, whereas UFW and firewalld build on top of those to offer simpler interfaces for common scenarios. And iptables vs nftables is about old versus new technology: nftables aims to eventually replace iptables with a more efficient and unified system, but iptables is still fully functional and in use, often through compatibility layers. Many firewall frontends (like UFW, firewalld) have adapted to use nftables on systems where iptables is deprecated, but conceptually, if you understand iptables, that knowledge transfers to nftables (the concepts of tables, chains, rules, and matches/targets -- or \"verdicts\" in nftables -- remain, just with different syntax and capabilities).",[17,7909,7910],{},"Summary: iptables is a robust, if sometimes complex, tool at the heart of Linux firewalling. Tools like UFW and firewalld simplify firewall configuration by abstracting iptables/nftables rules into easier commands or zone policies, which is great for quick setups or less experienced users. nftables is the newer framework that improves on iptables' design, offering better performance and a more streamlined rule language, representing the future of Linux packet filtering. Regardless of which tool is used on the surface, the fundamental ideas -- defining how packets are handled based on conditions -- remain the same. Understanding iptables' tables, chains, and rule logic gives you a strong foundation for working with any of these firewall tools and for securing Linux networks effectively.",[1697,7912,7913],{},"html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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":15,"searchDepth":25,"depth":25,"links":7915},[7916,7917,7918,7919,7925],{"id":7000,"depth":25,"text":7001},{"id":7044,"depth":25,"text":7045},{"id":7136,"depth":25,"text":7137},{"id":7335,"depth":25,"text":7336,"children":7920},[7921,7922,7923,7924],{"id":7345,"depth":215,"text":7346},{"id":7463,"depth":215,"text":7464},{"id":7613,"depth":215,"text":7614},{"id":7690,"depth":215,"text":7691},{"id":7857,"depth":25,"text":7858},"devops","A beginner-friendly introduction to configuring the Linux firewall using iptables.","/knowledge-base/iptables.png",{},"/knowledge-base/iptables",{"title":6985,"description":7927},{"loc":7930},"knowledge-base/iptables",[7143,1723,7935,2386],"Networking","0KsxjXeELhqlPpVUJOSESV2dlTeDO6Y77lTCIYGdFjI",{"id":7938,"title":7939,"body":7940,"category":8538,"contentType":8539,"date":1712,"description":8540,"extension":28,"head":27,"image":8541,"meta":8542,"navigation":30,"ogImage":27,"path":8543,"readingTime":1717,"robots":27,"schemaOrg":27,"seo":8544,"sitemap":8545,"stem":8546,"tags":8547,"__hash__":8551},"knowledgeBase/knowledge-base/jq-cheatsheet.md","Jq logs Cheat sheet",{"type":7,"value":7941,"toc":8523},[7942,7946,7952,8024,8028,8049,8053,8079,8083,8107,8111,8135,8138,8162,8166,8192,8196,8221,8225,8269,8273,8301,8303,8307,8310,8317,8340,8347,8379,8393,8413,8420,8449,8456,8486,8488,8492,8517,8520],[1737,7943,7945],{"id":7944},"file-based-logs-ndjson-jq","File-based logs (NDJSON + jq)",[17,7947,7948,7949,1883],{},"Assume your log files live under ",[49,7950,7951],{},"logs/",[1464,7953,7954,7964],{},[1467,7955,7956],{},[1470,7957,7958,7961],{},[1473,7959,7960],{},"File",[1473,7962,7963],{},"Contains levels ≥ ...",[1481,7965,7966,7986,8001,8014],{},[1470,7967,7968,7973],{},[1486,7969,7970],{},[49,7971,7972],{},"info.log",[1486,7974,7975,1520,7977,1520,7980,1520,7983],{},[49,7976,1966],{},[49,7978,7979],{},"warn",[49,7981,7982],{},"error",[49,7984,7985],{},"fatal",[1470,7987,7988,7993],{},[1486,7989,7990],{},[49,7991,7992],{},"warn.log",[1486,7994,7995,1520,7997,1520,7999],{},[49,7996,7979],{},[49,7998,7982],{},[49,8000,7985],{},[1470,8002,8003,8008],{},[1486,8004,8005],{},[49,8006,8007],{},"errors.log",[1486,8009,8010,1520,8012],{},[49,8011,7982],{},[49,8013,7985],{},[1470,8015,8016,8021],{},[1486,8017,8018],{},[49,8019,8020],{},"http.log",[1486,8022,8023],{},"HTTP middleware entries",[58,8025,8027],{"id":8026},"_1-follow-pretty-print-all-entries","1. Follow & pretty-print all entries",[66,8029,8031],{"className":68,"code":8030,"language":71,"meta":15,"style":15},"tail -f logs/info.log | jq .\n",[49,8032,8033],{"__ignoreMap":15},[75,8034,8035,8038,8040,8043,8045,8047],{"class":77,"line":78},[75,8036,8037],{"class":190},"tail",[75,8039,770],{"class":171},[75,8041,8042],{"class":92}," logs/info.log",[75,8044,573],{"class":81},[75,8046,812],{"class":190},[75,8048,815],{"class":92},[58,8050,8052],{"id":8051},"_2-only-errors","2. Only errors",[66,8054,8056],{"className":68,"code":8055,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq 'select(.level==\"error\")'\n",[49,8057,8058,8069],{"__ignoreMap":15},[75,8059,8060,8062,8064,8066],{"class":77,"line":78},[75,8061,8037],{"class":190},[75,8063,770],{"class":171},[75,8065,8042],{"class":92},[75,8067,8068],{"class":171},"\\\n",[75,8070,8071,8074,8076],{"class":77,"line":25},[75,8072,8073],{"class":81},"  |",[75,8075,812],{"class":190},[75,8077,8078],{"class":92}," 'select(.level==\"error\")'\n",[58,8080,8082],{"id":8081},"_3-only-pure-info-level-30","3. Only pure \"info\" (level 30)",[66,8084,8086],{"className":68,"code":8085,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq 'select(.level==30)'\n",[49,8087,8088,8098],{"__ignoreMap":15},[75,8089,8090,8092,8094,8096],{"class":77,"line":78},[75,8091,8037],{"class":190},[75,8093,770],{"class":171},[75,8095,8042],{"class":92},[75,8097,8068],{"class":171},[75,8099,8100,8102,8104],{"class":77,"line":25},[75,8101,8073],{"class":81},[75,8103,812],{"class":190},[75,8105,8106],{"class":92}," 'select(.level==30)'\n",[58,8108,8110],{"id":8109},"_4-filter-by-your-service-or-key","4. Filter by your service or key",[66,8112,8114],{"className":68,"code":8113,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq 'select(.service==\"BOT DETECTOR\")'\n",[49,8115,8116,8126],{"__ignoreMap":15},[75,8117,8118,8120,8122,8124],{"class":77,"line":78},[75,8119,8037],{"class":190},[75,8121,770],{"class":171},[75,8123,8042],{"class":92},[75,8125,8068],{"class":171},[75,8127,8128,8130,8132],{"class":77,"line":25},[75,8129,8073],{"class":81},[75,8131,812],{"class":190},[75,8133,8134],{"class":92}," 'select(.service==\"BOT DETECTOR\")'\n",[17,8136,8137],{},"Or BFF util:",[66,8139,8141],{"className":68,"code":8140,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq 'select(.Utils==\"BFF TO API\")'\n",[49,8142,8143,8153],{"__ignoreMap":15},[75,8144,8145,8147,8149,8151],{"class":77,"line":78},[75,8146,8037],{"class":190},[75,8148,770],{"class":171},[75,8150,8042],{"class":92},[75,8152,8068],{"class":171},[75,8154,8155,8157,8159],{"class":77,"line":25},[75,8156,8073],{"class":81},[75,8158,812],{"class":190},[75,8160,8161],{"class":92}," 'select(.Utils==\"BFF TO API\")'\n",[58,8163,8165],{"id":8164},"_5-extract-timestamp-message","5. Extract timestamp + message",[66,8167,8169],{"className":68,"code":8168,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq -r '.time + \" \" + .msg'\n",[49,8170,8171,8181],{"__ignoreMap":15},[75,8172,8173,8175,8177,8179],{"class":77,"line":78},[75,8174,8037],{"class":190},[75,8176,770],{"class":171},[75,8178,8042],{"class":92},[75,8180,8068],{"class":171},[75,8182,8183,8185,8187,8189],{"class":77,"line":25},[75,8184,8073],{"class":81},[75,8186,812],{"class":190},[75,8188,175],{"class":171},[75,8190,8191],{"class":92}," '.time + \" \" + .msg'\n",[58,8193,8195],{"id":8194},"_6-show-only-slow-http-requests-100-ms","6. Show only slow HTTP requests (>100 ms)",[66,8197,8199],{"className":68,"code":8198,"language":71,"meta":15,"style":15},"tail -f logs/http.log\\\n  | jq 'select(.latency > 100)'\n",[49,8200,8201,8212],{"__ignoreMap":15},[75,8202,8203,8205,8207,8210],{"class":77,"line":78},[75,8204,8037],{"class":190},[75,8206,770],{"class":171},[75,8208,8209],{"class":92}," logs/http.log",[75,8211,8068],{"class":171},[75,8213,8214,8216,8218],{"class":77,"line":25},[75,8215,8073],{"class":81},[75,8217,812],{"class":190},[75,8219,8220],{"class":92}," 'select(.latency > 100)'\n",[58,8222,8224],{"id":8223},"_7-live-count-per-level","7. Live count per level",[66,8226,8228],{"className":68,"code":8227,"language":71,"meta":15,"style":15},"tail -f logs/info.log\\\n  | jq -nR 'while (input?; .) | fromjson? | .level'\\\n  | sort | uniq -c\n",[49,8229,8230,8240,8254],{"__ignoreMap":15},[75,8231,8232,8234,8236,8238],{"class":77,"line":78},[75,8233,8037],{"class":190},[75,8235,770],{"class":171},[75,8237,8042],{"class":92},[75,8239,8068],{"class":171},[75,8241,8242,8244,8246,8249,8252],{"class":77,"line":25},[75,8243,8073],{"class":81},[75,8245,812],{"class":190},[75,8247,8248],{"class":171}," -nR",[75,8250,8251],{"class":92}," 'while (input?; .) | fromjson? | .level'",[75,8253,8068],{"class":171},[75,8255,8256,8258,8261,8263,8266],{"class":77,"line":215},[75,8257,8073],{"class":81},[75,8259,8260],{"class":190}," sort",[75,8262,573],{"class":81},[75,8264,8265],{"class":190}," uniq",[75,8267,8268],{"class":171}," -c\n",[58,8270,8272],{"id":8271},"_8-grep-first-for-speed-then-jq","8. Grep first for speed, then jq",[66,8274,8276],{"className":68,"code":8275,"language":71,"meta":15,"style":15},"grep --line-buffered '\"userService\"' logs/app.log\\\n  | jq .\n",[49,8277,8278,8293],{"__ignoreMap":15},[75,8279,8280,8282,8285,8288,8291],{"class":77,"line":78},[75,8281,143],{"class":190},[75,8283,8284],{"class":171}," --line-buffered",[75,8286,8287],{"class":92}," '\"userService\"'",[75,8289,8290],{"class":92}," logs/app.log",[75,8292,8068],{"class":171},[75,8294,8295,8297,8299],{"class":77,"line":25},[75,8296,8073],{"class":81},[75,8298,812],{"class":190},[75,8300,815],{"class":92},[1414,8302],{},[1737,8304,8306],{"id":8305},"systemd-journal-json-journalctl","🐳 systemd journal (JSON + journalctl)",[17,8308,8309],{},"If you've got your service running under systemd and you want the same JSON-style queries:",[3245,8311,8312],{},[1745,8313,8314],{},[1748,8315,8316],{},"Follow live with JSON output",[66,8318,8320],{"className":68,"code":8319,"language":71,"meta":15,"style":15},"    journalctl -u myapp.service -f -o json\n",[49,8321,8322],{"__ignoreMap":15},[75,8323,8324,8327,8330,8333,8335,8337],{"class":77,"line":78},[75,8325,8326],{"class":190},"    journalctl",[75,8328,8329],{"class":171}," -u",[75,8331,8332],{"class":92}," myapp.service",[75,8334,770],{"class":171},[75,8336,7659],{"class":171},[75,8338,8339],{"class":92}," json\n",[3245,8341,8342],{"start":25},[1745,8343,8344],{},[1748,8345,8346],{},"Pipe to jq",[66,8348,8350],{"className":68,"code":8349,"language":71,"meta":15,"style":15},"    journalctl -u myapp.service -f -o json\\\n      | jq 'select(.MESSAGE | test(\"error\"; \"i\"))'\n",[49,8351,8352,8369],{"__ignoreMap":15},[75,8353,8354,8356,8358,8360,8362,8364,8367],{"class":77,"line":78},[75,8355,8326],{"class":190},[75,8357,8329],{"class":171},[75,8359,8332],{"class":92},[75,8361,770],{"class":171},[75,8363,7659],{"class":171},[75,8365,8366],{"class":92}," json",[75,8368,8068],{"class":171},[75,8370,8371,8374,8376],{"class":77,"line":25},[75,8372,8373],{"class":81},"      |",[75,8375,812],{"class":190},[75,8377,8378],{"class":92}," 'select(.MESSAGE | test(\"error\"; \"i\"))'\n",[3245,8380,8381],{"start":215},[1745,8382,8383,6358,8386,8388,8389,8392],{},[1748,8384,8385],{},"Filter by priority",[49,8387,6451],{},"=emerg → ",[49,8390,8391],{},"7","=debug)",[66,8394,8396],{"className":68,"code":8395,"language":71,"meta":15,"style":15},"    journalctl -u myapp.service -f -p err\n",[49,8397,8398],{"__ignoreMap":15},[75,8399,8400,8402,8404,8406,8408,8410],{"class":77,"line":78},[75,8401,8326],{"class":190},[75,8403,8329],{"class":171},[75,8405,8332],{"class":92},[75,8407,770],{"class":171},[75,8409,7371],{"class":171},[75,8411,8412],{"class":92}," err\n",[3245,8414,8415],{"start":221},[1745,8416,8417],{},[1748,8418,8419],{},"Since a time",[66,8421,8423],{"className":68,"code":8422,"language":71,"meta":15,"style":15},"    journalctl -u myapp.service --since \"1 hour ago\" -o json | jq .\n",[49,8424,8425],{"__ignoreMap":15},[75,8426,8427,8429,8431,8433,8436,8439,8441,8443,8445,8447],{"class":77,"line":78},[75,8428,8326],{"class":190},[75,8430,8329],{"class":171},[75,8432,8332],{"class":92},[75,8434,8435],{"class":171}," --since",[75,8437,8438],{"class":92}," \"1 hour ago\"",[75,8440,7659],{"class":171},[75,8442,8366],{"class":92},[75,8444,573],{"class":81},[75,8446,812],{"class":190},[75,8448,815],{"class":92},[3245,8450,8451],{"start":235},[1745,8452,8453],{},[1748,8454,8455],{},"Combine filtering",[66,8457,8459],{"className":68,"code":8458,"language":71,"meta":15,"style":15},"    journalctl -u myapp.service -f -o json\\\n      | jq 'select(.PRIORITY>=4 and .MESSAGE | test(\"refund\"))'\n",[49,8460,8461,8477],{"__ignoreMap":15},[75,8462,8463,8465,8467,8469,8471,8473,8475],{"class":77,"line":78},[75,8464,8326],{"class":190},[75,8466,8329],{"class":171},[75,8468,8332],{"class":92},[75,8470,770],{"class":171},[75,8472,7659],{"class":171},[75,8474,8366],{"class":92},[75,8476,8068],{"class":171},[75,8478,8479,8481,8483],{"class":77,"line":25},[75,8480,8373],{"class":81},[75,8482,812],{"class":190},[75,8484,8485],{"class":92}," 'select(.PRIORITY>=4 and .MESSAGE | test(\"refund\"))'\n",[1414,8487],{},[58,8489,8491],{"id":8490},"tips","Tips",[1742,8493,8494,8502,8510],{},[1745,8495,8496,8501],{},[1748,8497,8498],{},[49,8499,8500],{},"jq -c ..."," emits compact JSON (one line), faster to tail.",[1745,8503,8504,8509],{},[1748,8505,8506],{},[49,8507,8508],{},"--line-buffered"," on grep forces immediate output in pipes.",[1745,8511,8512,8513,8516],{},"For systemd, ",[49,8514,8515],{},"-o json-pretty"," gives human-readable multiline JSON.",[17,8518,8519],{},"Keep this snippet handy in your terminal, and you'll have full live observability of both your file-based Pino logs and anything running under systemd.",[1697,8521,8522],{},"html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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":15,"searchDepth":25,"depth":25,"links":8524},[8525,8535],{"id":7944,"depth":25,"text":7945,"children":8526},[8527,8528,8529,8530,8531,8532,8533,8534],{"id":8026,"depth":215,"text":8027},{"id":8051,"depth":215,"text":8052},{"id":8081,"depth":215,"text":8082},{"id":8109,"depth":215,"text":8110},{"id":8164,"depth":215,"text":8165},{"id":8194,"depth":215,"text":8195},{"id":8223,"depth":215,"text":8224},{"id":8271,"depth":215,"text":8272},{"id":8305,"depth":25,"text":8306,"children":8536},[8537],{"id":8490,"depth":215,"text":8491},"tools","recipe","Parsing and filtering JSON logs from the command line","/knowledge-base/jq.jpg",{},"/knowledge-base/jq-cheatsheet",{"title":7939,"description":8540},{"loc":8543},"knowledge-base/jq-cheatsheet",[8548,3858,8549,8550],"jq","CLI","Logging","KsvBJxDv7cSeRvsnPB7yKocAF7PFFN55ww_my9z_I5E",{"id":8553,"title":8554,"body":8555,"category":6969,"contentType":2374,"date":1712,"description":15553,"extension":28,"head":27,"image":15554,"meta":15555,"navigation":30,"ogImage":27,"path":15556,"readingTime":15557,"robots":27,"schemaOrg":27,"seo":15558,"sitemap":15559,"stem":15560,"tags":15561,"__hash__":15563},"knowledgeBase/knowledge-base/jwt.md","JWT Implementation: Refresh Tokens vs. Private Key",{"type":7,"value":8556,"toc":15529},[8557,8561,8564,8567,8569,8573,8577,8588,8592,8611,8619,8623,8627,8655,8659,8675,8682,8687,8691,8697,8703,8706,8714,8718,8731,8735,9126,9132,9135,9138,9140,9144,9149,9157,9162,9317,9322,9329,9331,9335,9574,9579,9582,9585,9589,9606,9610,9613,9639,9642,9647,9726,9731,9779,9784,9788,9791,9802,9810,12558,12561,12566,12613,12623,12629,12633,12636,12720,12739,12743,12918,12925,12930,12935,13091,13095,13101,13124,13535,13555,13559,13563,13566,13572,13575,13579,13582,13585,13589,13598,13601,13607,13611,13614,13617,13621,13655,13659,13662,13666,13771,13775,13778,13786,13790,13804,13808,13811,13815,14190,14204,14208,14953,14970,14974,14978,14986,14990,14998,15002,15007,15011,15076,15080,15086,15093,15096,15100,15103,15108,15111,15115,15143,15151,15155,15190,15215,15219,15222,15226,15266,15274,15278,15289,15293,15314,15318,15354,15365,15369,15382,15387,15391,15415,15419,15426,15445,15449,15453,15467,15471,15482,15486,15494,15498,15510,15518,15523,15526],[1737,8558,8560],{"id":8559},"part-1-jwt-implementation-refresh-tokens-vs-private-key","Part 1: JWT Implementation: Refresh Tokens vs. Private Key",[17,8562,8563],{},"This guide provides a comprehensive breakdown of how refresh tokens and access tokens work within a JWT authentication flow. It explains why a refresh token should never be treated as a “private key,” and how to architect signing and verification securely.",[17,8565,8566],{},"A refresh token is not a substitute for your cryptographic signing key. Instead, the signing key (HMAC secret or RSA/EC private key) must remain strictly on the server, while the refresh token serves as a long‐lived credential that is stored and validated server‐side.",[1414,8568],{},[58,8570,8572],{"id":8571},"_1-jwt-signing-verification-the-real-keys","1. JWT Signing & Verification: The Real “Keys”",[3477,8574,8576],{"id":8575},"signing-key-secret-or-private-key","Signing Key (Secret or Private Key)",[1742,8578,8579,8582,8585],{},[1745,8580,8581],{},"This key lives only on your server (or in a secured KMS/HSM).",[1745,8583,8584],{},"You use it to sign every access‐token JWT you issue.",[1745,8586,8587],{},"It must never be shared with clients. If it leaks, anyone can forge valid JWTs.",[3477,8589,8591],{"id":8590},"verification-key-same-secret-or-a-public-key","Verification Key (Same Secret or a Public Key)",[1742,8593,8594,8600],{},[1745,8595,8596,8597,1802],{},"If you use HMAC (e.g. HS256), then signing and verification use the same secret. Clients never see that secret; your server just calls ",[49,8598,8599],{},"jwt.verify(...)",[1745,8601,8602,8603],{},"If you use asymmetric signing (e.g. RS256/ES256), then you sign with your private key and verify with the public key.",[1742,8604,8605,8608],{},[1745,8606,8607],{},"You can distribute the public key to downstream services (or embed it in your resource server) so they can independently check the JWT’s signature.",[1745,8609,8610],{},"But the private key stays on your auth server and is never exposed to clients.",[3960,8612,8613],{},[17,8614,8615,8618],{},[1748,8616,8617],{},"Key point:"," The JWT signing key (whether a shared HMAC secret or an RSA/EC private key) is the true >“private key” in the system. Clients only ever get a signed token (the access JWT), not the signing key itself.",[58,8620,8622],{"id":8621},"_2-access-token-vs-refresh-token-what-each-really-is","2. Access Token vs. Refresh Token: What Each Really Is",[3477,8624,8626],{"id":8625},"access-token-jwt","Access Token (JWT)",[1742,8628,8629,8632,8635,8638,8645,8652],{},[1745,8630,8631],{},"A short‐lived, self‐contained credential.",[1745,8633,8634],{},"Contains claims (e.g. user ID, roles, expiration) signed by your server.",[1745,8636,8637],{},"Bearer‐style: whoever holds it can “act as” that user for the duration of its lifetime (for example, 10–15 minutes).",[1745,8639,8640,8641,8644],{},"Sent on every API request in the ",[49,8642,8643],{},"Authorization: Bearer \u003Caccess_token>"," header.",[1745,8646,8647,8648,8651],{},"Resource servers verify its signature (with the HMAC secret or with your public key) and check the ",[49,8649,8650],{},"exp"," claim.",[1745,8653,8654],{},"Once it expires, clients must use a valid refresh token to get a new access token.",[3477,8656,8658],{"id":8657},"refresh-token","Refresh Token",[1742,8660,8661,8664],{},[1745,8662,8663],{},"A long‐lived credential whose sole purpose is to obtain new access tokens.",[1745,8665,8666,8667],{},"It is typically:",[3245,8668,8669,8672],{},[1745,8670,8671],{},"A random, high‐entropy opaque string (e.g. a UUID or a securely generated random byte sequence), or",[1745,8673,8674],{},"A JWT with a very long expiration (less common—because if a long‐lived JWT leaks, it’s riskier).",[3960,8676,8677],{},[17,8678,8679,8681],{},[1748,8680,8617],{}," The refresh token is not a signing key. It is just a bearer credential that your server checks against a database (or an in-memory store, or Redis) to see “Is this refresh token still valid/issued?”",[1742,8683,8684],{},[1745,8685,8686],{},"Stored by the client in an HTTP-only, secure cookie (or in secure storage if you’re building an SPA with strict XSS protections). The client never “sees” the raw refresh‐token string if you use cookies with the HttpOnly flag.",[58,8688,8690],{"id":8689},"_3-why-you-should-not-treat-a-refresh-token-as-a-private-key","3. Why You Should Not Treat a Refresh Token as a “Private Key”",[17,8692,8693,8696],{},[1748,8694,8695],{},"Private‐Key Role:"," A private key in a JWT setup is what actually signs the access token. It must remain secret, and is used mathematically (RSA/EC) or as an HMAC secret.",[17,8698,8699,8702],{},[1748,8700,8701],{},"Refresh Token Role:"," A refresh token is just a long‐lived opaque value (or a long‐lived JWT). It’s stored server-side (e.g. in a database table or Redis) so that when the client presents it, you can look up its status (still valid? user not disabled? device not blocked?).",[17,8704,8705],{},"If you were to give a client something that it could use to sign future access tokens, they could mint any access token they want. That completely breaks security.",[3960,8707,8708],{},[17,8709,8710,8713],{},[1748,8711,8712],{},"In other words:"," If you hand out your “refresh token” as if it were your signing private key, the client could just create a fresh, valid access‐token JWT at will—bypassing every check. That is why your signing key can never leave your backend environment.",[58,8715,8717],{"id":8716},"_4-a-proper-jwt-flow-in-nodejs-hs256-or-rs256","4. A Proper JWT Flow in Node.js (HS256 or RS256)",[17,8719,8720,8721,1520,8724,1520,8727,8730],{},"The following is a canonical way to implement JWT + refresh token logic in Node.js/Express. The exact library calls will depend on whichever JWT library you choose (e.g. ",[1748,8722,8723],{},"jsonwebtoken",[1748,8725,8726],{},"jose",[1748,8728,8729],{},"@auth0/node-jsonwebtoken",", etc.), but the pattern remains:",[3477,8732,8734],{"id":8733},"_41-at-login-issue-both-tokens","4.1 At Login (Issue Both Tokens)",[3245,8736,8737,8743,8921,8999],{},[1745,8738,8739,8742],{},[1748,8740,8741],{},"Authenticate User Credentials"," (e.g. email/password)",[1745,8744,8745,8748,8750,8751,8822,8824,8827,8842],{},[1748,8746,8747],{},"Create an Access Token (JWT)",[6038,8749],{},"Payload might include:",[66,8752,8754],{"className":2039,"code":8753,"language":2041,"meta":15,"style":15},"{\n  sub: user.id,\n  name: user.name,\n  roles: user.roles,\n  iat: \u003Cnow>,\n  exp: \u003Cnow + 15 minutes>\n}\n",[49,8755,8756,8761,8769,8777,8785,8799,8818],{"__ignoreMap":15},[75,8757,8758],{"class":77,"line":78},[75,8759,8760],{"class":85},"{\n",[75,8762,8763,8766],{"class":77,"line":25},[75,8764,8765],{"class":190},"  sub",[75,8767,8768],{"class":85},": user.id,\n",[75,8770,8771,8774],{"class":77,"line":215},[75,8772,8773],{"class":190},"  name",[75,8775,8776],{"class":85},": user.name,\n",[75,8778,8779,8782],{"class":77,"line":221},[75,8780,8781],{"class":190},"  roles",[75,8783,8784],{"class":85},": user.roles,\n",[75,8786,8787,8790,8793,8796],{"class":77,"line":235},[75,8788,8789],{"class":190},"  iat",[75,8791,8792],{"class":85},": \u003C",[75,8794,8795],{"class":1879},"now",[75,8797,8798],{"class":85},">,\n",[75,8800,8801,8804,8806,8810,8813,8816],{"class":77,"line":248},[75,8802,8803],{"class":85},"  exp: \u003C",[75,8805,8795],{"class":1879},[75,8807,8809],{"class":8808},"sVAnh"," +",[75,8811,8812],{"class":8808}," 15",[75,8814,8815],{"class":190}," minutes",[75,8817,7208],{"class":85},[75,8819,8820],{"class":77,"line":261},[75,8821,325],{"class":85},[6038,8823],{},[1748,8825,8826],{},"Sign it with:",[1742,8828,8829,8835],{},[1745,8830,8831,8832,2030],{},"For HS256: a server‐only secret (e.g. ",[49,8833,8834],{},"process.env.JWT_SECRET",[1745,8836,8837,8838,8841],{},"For RS256: your RSA/EC private key (loaded via ",[49,8839,8840],{},"fs.readFileSync + crypto.createPrivateKey(...)"," or a JWK).",[66,8843,8845],{"className":2039,"code":8844,"language":2041,"meta":15,"style":15},"import jwt from 'jsonwebtoken';\n// HS256 example:\nconst accessToken = jwt.sign(\n  { sub: user.id, name: user.name, roles: user.roles },\n  process.env.JWT_SECRET,               // keep this secret on server only\n  { algorithm: 'HS256', expiresIn: '15m' }\n);\n",[49,8846,8847,8861,8866,8882,8887,8901,8917],{"__ignoreMap":15},[75,8848,8849,8851,8854,8856,8859],{"class":77,"line":78},[75,8850,2593],{"class":81},[75,8852,8853],{"class":85}," jwt ",[75,8855,2599],{"class":81},[75,8857,8858],{"class":92}," 'jsonwebtoken'",[75,8860,2605],{"class":85},[75,8862,8863],{"class":77,"line":25},[75,8864,8865],{"class":2542},"// HS256 example:\n",[75,8867,8868,8870,8873,8875,8878,8880],{"class":77,"line":215},[75,8869,2490],{"class":81},[75,8871,8872],{"class":171}," accessToken",[75,8874,605],{"class":81},[75,8876,8877],{"class":85}," jwt.",[75,8879,2501],{"class":190},[75,8881,2197],{"class":85},[75,8883,8884],{"class":77,"line":221},[75,8885,8886],{"class":85},"  { sub: user.id, name: user.name, roles: user.roles },\n",[75,8888,8889,8892,8895,8898],{"class":77,"line":235},[75,8890,8891],{"class":85},"  process.env.",[75,8893,8894],{"class":171},"JWT_SECRET",[75,8896,8897],{"class":85},",               ",[75,8899,8900],{"class":2542},"// keep this secret on server only\n",[75,8902,8903,8906,8909,8912,8915],{"class":77,"line":248},[75,8904,8905],{"class":85},"  { algorithm: ",[75,8907,8908],{"class":92},"'HS256'",[75,8910,8911],{"class":85},", expiresIn: ",[75,8913,8914],{"class":92},"'15m'",[75,8916,3791],{"class":85},[75,8918,8919],{"class":77,"line":261},[75,8920,2160],{"class":85},[1745,8922,8923,8926,8928,8929,8976,4284,8978,8981,8982,8985,8986,2030,8989,8991,8992,8995,8996,1802],{},[1748,8924,8925],{},"Create a Refresh Token",[6038,8927],{},"Best practice: generate a totally random string, e.g.:",[66,8930,8932],{"className":2039,"code":8931,"language":2041,"meta":15,"style":15},"import { randomBytes } from 'crypto';\nconst refreshToken = randomBytes(32).toString('hex'); // 256-bit of randomness\n",[49,8933,8934,8947],{"__ignoreMap":15},[75,8935,8936,8938,8941,8943,8945],{"class":77,"line":78},[75,8937,2593],{"class":81},[75,8939,8940],{"class":85}," { randomBytes } ",[75,8942,2599],{"class":81},[75,8944,2602],{"class":92},[75,8946,2605],{"class":85},[75,8948,8949,8951,8954,8956,8959,8961,8963,8965,8967,8969,8971,8973],{"class":77,"line":25},[75,8950,2490],{"class":81},[75,8952,8953],{"class":171}," refreshToken",[75,8955,605],{"class":81},[75,8957,8958],{"class":190}," randomBytes",[75,8960,2135],{"class":85},[75,8962,6456],{"class":171},[75,8964,2030],{"class":85},[75,8966,3467],{"class":190},[75,8968,2135],{"class":85},[75,8970,3640],{"class":92},[75,8972,3310],{"class":85},[75,8974,8975],{"class":2542},"// 256-bit of randomness\n",[6038,8977],{},[49,8979,8980],{},"refreshToken"," in your database or Redis, associated with ",[49,8983,8984],{},"user.id"," and a rotation/expiry timestamp (e.g. ",[49,8987,8988],{},"expiresAt = now + 30 days",[6038,8990],{},"Optionally hash the refresh token in the database (so your DB doesn’t store the raw token if it ever leaks). E.g. ",[49,8993,8994],{},"hashed = sha256(refreshToken)"," and store ",[49,8997,8998],{},"hashed + userId",[1745,9000,9001,9004,9018],{},[1748,9002,9003],{},"Send Both Tokens to the Client",[1742,9005,9006,9012],{},[1745,9007,9008,9011],{},[1748,9009,9010],{},"Access Token:"," Usually returned in the JSON response body.",[1745,9013,9014,9017],{},[1748,9015,9016],{},"Refresh Token:"," Send as an HTTP-only, Secure cookie:",[66,9019,9021],{"className":2039,"code":9020,"language":2041,"meta":15,"style":15},"// Example Express route handler\nres\n  .cookie('refreshToken', refreshToken, {\n    httpOnly: true,\n    secure: true,       // in production, only over HTTPS\n    path: '/auth/refresh',\n    maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days\n  })\n  .json({ accessToken });\n",[49,9022,9023,9028,9033,9048,9057,9070,9080,9111,9116],{"__ignoreMap":15},[75,9024,9025],{"class":77,"line":78},[75,9026,9027],{"class":2542},"// Example Express route handler\n",[75,9029,9030],{"class":77,"line":25},[75,9031,9032],{"class":85},"res\n",[75,9034,9035,9037,9040,9042,9045],{"class":77,"line":215},[75,9036,6407],{"class":85},[75,9038,9039],{"class":190},"cookie",[75,9041,2135],{"class":85},[75,9043,9044],{"class":92},"'refreshToken'",[75,9046,9047],{"class":85},", refreshToken, {\n",[75,9049,9050,9053,9055],{"class":77,"line":221},[75,9051,9052],{"class":85},"    httpOnly: ",[75,9054,1826],{"class":171},[75,9056,2205],{"class":85},[75,9058,9059,9062,9064,9067],{"class":77,"line":235},[75,9060,9061],{"class":85},"    secure: ",[75,9063,1826],{"class":171},[75,9065,9066],{"class":85},",       ",[75,9068,9069],{"class":2542},"// in production, only over HTTPS\n",[75,9071,9072,9075,9078],{"class":77,"line":248},[75,9073,9074],{"class":85},"    path: ",[75,9076,9077],{"class":92},"'/auth/refresh'",[75,9079,2205],{"class":85},[75,9081,9082,9085,9088,9091,9094,9096,9099,9101,9103,9105,9108],{"class":77,"line":261},[75,9083,9084],{"class":85},"    maxAge: ",[75,9086,9087],{"class":171},"30",[75,9089,9090],{"class":81}," *",[75,9092,9093],{"class":171}," 24",[75,9095,9090],{"class":81},[75,9097,9098],{"class":171}," 60",[75,9100,9090],{"class":81},[75,9102,9098],{"class":171},[75,9104,9090],{"class":81},[75,9106,9107],{"class":171}," 1000",[75,9109,9110],{"class":2542}," // 30 days\n",[75,9112,9113],{"class":77,"line":274},[75,9114,9115],{"class":85},"  })\n",[75,9117,9118,9120,9123],{"class":77,"line":615},[75,9119,6407],{"class":85},[75,9121,9122],{"class":190},"json",[75,9124,9125],{"class":85},"({ accessToken });\n",[17,9127,9128,9129],{},"🔒 ",[1748,9130,9131],{},"Why not return the refresh token in JSON?",[17,9133,9134],{},"If you return it in JSON, front-end code could read it (localStorage), making it vulnerable to XSS.",[17,9136,9137],{},"By using an HTTP-only cookie, you force the browser to send it automatically, but JS can’t inspect it.",[1414,9139],{},[3477,9141,9143],{"id":9142},"_42-on-every-api-call-protecting-routes","4.2 On Every API Call (Protecting Routes)",[17,9145,9146],{},[1748,9147,9148],{},"Client Sends Access Token in Header",[66,9150,9155],{"className":9151,"code":9153,"language":9154},[9152],"language-text","GET /api/protected\nAuthorization: Bearer \u003CaccessToken>\n","text",[49,9156,9153],{"__ignoreMap":15},[17,9158,9159],{},[1748,9160,9161],{},"Server Middleware Verifies Access Token",[1742,9163,9164,9247],{},[1745,9165,9166,9167],{},"With HS256:",[66,9168,9170],{"className":2039,"code":9169,"language":2041,"meta":15,"style":15},"try {\n  const payload = jwt.verify(token, process.env.JWT_SECRET);\n  req.user = { id: payload.sub, roles: payload.roles };\n  next();\n} catch (err) {\n  // token expired or invalid → return 401\n  res.sendStatus(401);\n}\n",[49,9171,9172,9179,9198,9208,9214,9225,9230,9243],{"__ignoreMap":15},[75,9173,9174,9177],{"class":77,"line":78},[75,9175,9176],{"class":81},"try",[75,9178,2076],{"class":85},[75,9180,9181,9183,9185,9187,9189,9191,9194,9196],{"class":77,"line":25},[75,9182,2081],{"class":81},[75,9184,3847],{"class":171},[75,9186,605],{"class":81},[75,9188,8877],{"class":85},[75,9190,2536],{"class":190},[75,9192,9193],{"class":85},"(token, process.env.",[75,9195,8894],{"class":171},[75,9197,2160],{"class":85},[75,9199,9200,9203,9205],{"class":77,"line":215},[75,9201,9202],{"class":85},"  req.user ",[75,9204,89],{"class":81},[75,9206,9207],{"class":85}," { id: payload.sub, roles: payload.roles };\n",[75,9209,9210,9212],{"class":77,"line":221},[75,9211,2302],{"class":190},[75,9213,2305],{"class":85},[75,9215,9216,9219,9222],{"class":77,"line":235},[75,9217,9218],{"class":85},"} ",[75,9220,9221],{"class":81},"catch",[75,9223,9224],{"class":85}," (err) {\n",[75,9226,9227],{"class":77,"line":248},[75,9228,9229],{"class":2542},"  // token expired or invalid → return 401\n",[75,9231,9232,9234,9236,9238,9241],{"class":77,"line":261},[75,9233,2174],{"class":85},[75,9235,2284],{"class":190},[75,9237,2135],{"class":85},[75,9239,9240],{"class":171},"401",[75,9242,2160],{"class":85},[75,9244,9245],{"class":77,"line":274},[75,9246,325],{"class":85},[1745,9248,9249,9250],{},"With RS256:",[66,9251,9253],{"className":2039,"code":9252,"language":2041,"meta":15,"style":15},"import fs from 'fs';\nconst publicKey = fs.readFileSync('/path/to/public.pem');\nconst payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] });\n\n// If valid → Proceed → req.user holds the user’s identity\n",[49,9254,9255,9267,9287,9308,9312],{"__ignoreMap":15},[75,9256,9257,9259,9261,9263,9265],{"class":77,"line":78},[75,9258,2593],{"class":81},[75,9260,2978],{"class":85},[75,9262,2599],{"class":81},[75,9264,2983],{"class":92},[75,9266,2605],{"class":85},[75,9268,9269,9271,9274,9276,9278,9280,9282,9285],{"class":77,"line":25},[75,9270,2490],{"class":81},[75,9272,9273],{"class":171}," publicKey",[75,9275,605],{"class":81},[75,9277,2997],{"class":85},[75,9279,3000],{"class":190},[75,9281,2135],{"class":85},[75,9283,9284],{"class":92},"'/path/to/public.pem'",[75,9286,2160],{"class":85},[75,9288,9289,9291,9293,9295,9297,9299,9302,9305],{"class":77,"line":215},[75,9290,2490],{"class":81},[75,9292,3847],{"class":171},[75,9294,605],{"class":81},[75,9296,8877],{"class":85},[75,9298,2536],{"class":190},[75,9300,9301],{"class":85},"(token, publicKey, { algorithms: [",[75,9303,9304],{"class":92},"'RS256'",[75,9306,9307],{"class":85},"] });\n",[75,9309,9310],{"class":77,"line":221},[75,9311,527],{"emptyLinePlaceholder":30},[75,9313,9314],{"class":77,"line":235},[75,9315,9316],{"class":2542},"// If valid → Proceed → req.user holds the user’s identity\n",[17,9318,9319],{},[1748,9320,9321],{},"If Invalid/Expired → Return 401 Unauthorized",[3960,9323,9324],{},[17,9325,9326,9328],{},[1748,9327,8617],{}," Access tokens are verified using your server‐only HMAC secret or your server’s public key (if you used RS256). The client never sees the HMAC secret or RSA private key.",[1414,9330],{},[3477,9332,9334],{"id":9333},"_43-when-access-token-expires-using-the-refresh-token","4.3 When Access Token Expires (Using the Refresh Token)",[3245,9336,9337,9343,9367,9568],{},[1745,9338,9339,9342],{},[1748,9340,9341],{},"Client Detects 401"," from Protected Route (Expired Access Token)",[1745,9344,9345,9348,9349,9352,9353,9356,9357,9359,9360,9362,9363,9366],{},[1748,9346,9347],{},"Front-end code makes a request to"," ",[49,9350,9351],{},"/auth/refresh"," (e.g. ",[49,9354,9355],{},"POST /auth/refresh",") with no body.",[6038,9358],{},"Because you set ",[49,9361,8980],{}," as an HTTP-only cookie on ",[49,9364,9365],{},"Path=/auth/refresh",", the browser automatically includes that cookie.",[1745,9368,9369,9375,9377,9378,9394,9396,9397,9399,9400],{},[1748,9370,9371,9372,9374],{},"Server’s ",[49,9373,9351],{}," Handler",[6038,9376],{},"Extract the cookie:",[66,9379,9381],{"className":2039,"code":9380,"language":2041,"meta":15,"style":15},"const refreshToken = req.cookies.refreshToken;\n",[49,9382,9383],{"__ignoreMap":15},[75,9384,9385,9387,9389,9391],{"class":77,"line":78},[75,9386,2490],{"class":81},[75,9388,8953],{"class":171},[75,9390,605],{"class":81},[75,9392,9393],{"class":85}," req.cookies.refreshToken;\n",[6038,9395],{},"Look up ",[49,9398,8980],{}," (or its hash) in your database/Redis:",[66,9401,9403],{"className":2039,"code":9402,"language":2041,"meta":15,"style":15},"// 2a. Assume you stored `hashedToken = sha256(refreshToken)` when you first issued it.\nconst hashed = sha256(refreshTokenFromCookie);\nconst record = await db.findRefreshToken(hashed);\nif (!record) throw new Error('Invalid refresh token');\n\n// 2b. If valid, generate a new access token:\nconst newAccessToken = jwt.sign(\n  { sub: record.userId, roles: record.userRoles },\n  process.env.JWT_SECRET,\n  { algorithm: 'HS256', expiresIn: '15m' }\n);\n\n// 2c. (Optional) implement “rotate on use”:\n//      i. Remove the old refresh token record from DB.\n//     ii. Generate a new random refreshToken (randomBytes(32).toString('hex'))\n//    iii. Store new hashedRefreshToken in DB with new expiry.\n//     iv. Set new HTTP-only cookie: res.cookie('refreshToken', newRefreshToken, …)\n\nreturn res.json({ accessToken: newAccessToken });\n",[49,9404,9405,9410,9425,9446,9470,9474,9479,9494,9499,9507,9519,9523,9527,9532,9537,9542,9547,9552,9556],{"__ignoreMap":15},[75,9406,9407],{"class":77,"line":78},[75,9408,9409],{"class":2542},"// 2a. Assume you stored `hashedToken = sha256(refreshToken)` when you first issued it.\n",[75,9411,9412,9414,9417,9419,9422],{"class":77,"line":25},[75,9413,2490],{"class":81},[75,9415,9416],{"class":171}," hashed",[75,9418,605],{"class":81},[75,9420,9421],{"class":190}," sha256",[75,9423,9424],{"class":85},"(refreshTokenFromCookie);\n",[75,9426,9427,9429,9432,9434,9437,9440,9443],{"class":77,"line":215},[75,9428,2490],{"class":81},[75,9430,9431],{"class":171}," record",[75,9433,605],{"class":81},[75,9435,9436],{"class":81}," await",[75,9438,9439],{"class":85}," db.",[75,9441,9442],{"class":190},"findRefreshToken",[75,9444,9445],{"class":85},"(hashed);\n",[75,9447,9448,9450,9452,9454,9457,9459,9461,9463,9465,9468],{"class":77,"line":221},[75,9449,154],{"class":81},[75,9451,6358],{"class":85},[75,9453,6361],{"class":81},[75,9455,9456],{"class":85},"record) ",[75,9458,5264],{"class":81},[75,9460,6372],{"class":81},[75,9462,6375],{"class":190},[75,9464,2135],{"class":85},[75,9466,9467],{"class":92},"'Invalid refresh token'",[75,9469,2160],{"class":85},[75,9471,9472],{"class":77,"line":235},[75,9473,527],{"emptyLinePlaceholder":30},[75,9475,9476],{"class":77,"line":248},[75,9477,9478],{"class":2542},"// 2b. If valid, generate a new access token:\n",[75,9480,9481,9483,9486,9488,9490,9492],{"class":77,"line":261},[75,9482,2490],{"class":81},[75,9484,9485],{"class":171}," newAccessToken",[75,9487,605],{"class":81},[75,9489,8877],{"class":85},[75,9491,2501],{"class":190},[75,9493,2197],{"class":85},[75,9495,9496],{"class":77,"line":274},[75,9497,9498],{"class":85},"  { sub: record.userId, roles: record.userRoles },\n",[75,9500,9501,9503,9505],{"class":77,"line":615},[75,9502,8891],{"class":85},[75,9504,8894],{"class":171},[75,9506,2205],{"class":85},[75,9508,9509,9511,9513,9515,9517],{"class":77,"line":636},[75,9510,8905],{"class":85},[75,9512,8908],{"class":92},[75,9514,8911],{"class":85},[75,9516,8914],{"class":92},[75,9518,3791],{"class":85},[75,9520,9521],{"class":77,"line":642},[75,9522,2160],{"class":85},[75,9524,9525],{"class":77,"line":663},[75,9526,527],{"emptyLinePlaceholder":30},[75,9528,9529],{"class":77,"line":669},[75,9530,9531],{"class":2542},"// 2c. (Optional) implement “rotate on use”:\n",[75,9533,9534],{"class":77,"line":675},[75,9535,9536],{"class":2542},"//      i. Remove the old refresh token record from DB.\n",[75,9538,9539],{"class":77,"line":684},[75,9540,9541],{"class":2542},"//     ii. Generate a new random refreshToken (randomBytes(32).toString('hex'))\n",[75,9543,9544],{"class":77,"line":690},[75,9545,9546],{"class":2542},"//    iii. Store new hashedRefreshToken in DB with new expiry.\n",[75,9548,9549],{"class":77,"line":2254},[75,9550,9551],{"class":2542},"//     iv. Set new HTTP-only cookie: res.cookie('refreshToken', newRefreshToken, …)\n",[75,9553,9554],{"class":77,"line":2259},[75,9555,527],{"emptyLinePlaceholder":30},[75,9557,9558,9561,9563,9565],{"class":77,"line":2276},[75,9559,9560],{"class":81},"return",[75,9562,2281],{"class":85},[75,9564,9122],{"class":190},[75,9566,9567],{"class":85},"({ accessToken: newAccessToken });\n",[1745,9569,9570,9573],{},[1748,9571,9572],{},"Client Retries Original API Call"," with New Access Token.",[17,9575,9128,9576],{},[1748,9577,9578],{},"Security note:",[17,9580,9581],{},"If an attacker somehow steals a refresh token, they can keep minting new access tokens until that refresh token is revoked/expired.",[17,9583,9584],{},"That’s why you want to keep refresh tokens in an HttpOnly, Secure cookie, check IP/client fingerprints if needed, and rotate them on every use (so you can blacklist a stolen one).",[58,9586,9588],{"id":9587},"_5-summary-why-refresh-token-as-private-key-is-incorrect","5. Summary: Why “Refresh Token as Private Key” Is Incorrect",[1742,9590,9591,9594,9600,9603],{},[1745,9592,9593],{},"You never hand out your signing key—that is your “private key.” It stays on the server (or KMS).",[1745,9595,9596,9597,9599],{},"A refresh token is just a long‐lived credential you validate against a store. It is not used to ",[1795,9598,2501],{}," anything.",[1745,9601,9602],{},"Access tokens (JWTs) already contain the client-visible “proof” (the signed payload). Resource servers verify that proof with your HMAC secret (HS256) or with your public key (RS256).",[1745,9604,9605],{},"Treat the refresh token like a password or API key—only your back end can verify it (by looking it up in a database or by decrypting/verifying a signed JWT). Don’t let the client “sign” tokens or think of the refresh token as a signing key.",[58,9607,9609],{"id":9608},"_6-if-you-really-want-asymmetric-jwt-rs256-across-services","6. If You Really Want Asymmetric JWT (RS256) Across Services",[17,9611,9612],{},"Sometimes you issue an access token on Service A and want Service B (a separate microservice) to trust it without hitting a central database. In that scenario:",[3245,9614,9615,9624,9627,9633],{},[1745,9616,9617,9618,52,9621,2030],{},"Generate an RSA/EC key pair on your auth server (e.g. with ",[49,9619,9620],{},"openssl",[49,9622,9623],{},"crypto.generateKeyPairSync('rsa', …)",[1745,9625,9626],{},"Keep the private key on your auth server only; all JWTs are signed with that.",[1745,9628,9629,9630,1802],{},"Publish the public key (or JWK Set) to any downstream service that needs to verify access tokens—e.g. via a well‐known endpoint like ",[49,9631,9632],{},"https://auth.myapp.com/.well-known/jwks.json",[1745,9634,9635,9636,1802],{},"Downstream services load that public key (or periodically rotate and cache it), then call ",[49,9637,9638],{},"jwt.verify(token, publicKey, { algorithms: ['RS256'] })",[17,9640,9641],{},"Refresh token logic remains exactly the same: an opaque, server‐validated credential that never leaves your auth cluster.",[17,9643,9644],{},[1748,9645,9646],{},"Auth Server:",[1742,9648,9649,9656,9717,9720],{},[1745,9650,9651,9652,9655],{},"Holds ",[49,9653,9654],{},"private.pem"," (never exposed).",[1745,9657,9658,9659],{},"Signs JWT access tokens:",[66,9660,9662],{"className":2039,"code":9661,"language":2041,"meta":15,"style":15},"const accessToken = jwt.sign(\n  { sub: user.id, … },\n  fs.readFileSync('./private.pem'), // private key\n  { algorithm: 'RS256', expiresIn: '15m' }\n);\n",[49,9663,9664,9678,9683,9701,9713],{"__ignoreMap":15},[75,9665,9666,9668,9670,9672,9674,9676],{"class":77,"line":78},[75,9667,2490],{"class":81},[75,9669,8872],{"class":171},[75,9671,605],{"class":81},[75,9673,8877],{"class":85},[75,9675,2501],{"class":190},[75,9677,2197],{"class":85},[75,9679,9680],{"class":77,"line":25},[75,9681,9682],{"class":85},"  { sub: user.id, … },\n",[75,9684,9685,9688,9690,9692,9695,9698],{"class":77,"line":215},[75,9686,9687],{"class":85},"  fs.",[75,9689,3000],{"class":190},[75,9691,2135],{"class":85},[75,9693,9694],{"class":92},"'./private.pem'",[75,9696,9697],{"class":85},"), ",[75,9699,9700],{"class":2542},"// private key\n",[75,9702,9703,9705,9707,9709,9711],{"class":77,"line":221},[75,9704,8905],{"class":85},[75,9706,9304],{"class":92},[75,9708,8911],{"class":85},[75,9710,8914],{"class":92},[75,9712,3791],{"class":85},[75,9714,9715],{"class":77,"line":235},[75,9716,2160],{"class":85},[1745,9718,9719],{},"Creates/stores refresh tokens in DB.",[1745,9721,9722,9723,9725],{},"Exposes ",[49,9724,9351],{}," that verifies refresh tokens and issues new JWTs.",[17,9727,9728],{},[1748,9729,9730],{},"API Gateway / Microservice:",[1742,9732,9733,9739,9776],{},[1745,9734,9651,9735,9738],{},[49,9736,9737],{},"public.pem"," (only the public half).",[1745,9740,9741,9742],{},"Verifies incoming JWTs:",[66,9743,9745],{"className":2039,"code":9744,"language":2041,"meta":15,"style":15},"const payload = jwt.verify(token, fs.readFileSync('./public.pem'), { algorithms: ['RS256'] });\n",[49,9746,9747],{"__ignoreMap":15},[75,9748,9749,9751,9753,9755,9757,9759,9762,9764,9766,9769,9772,9774],{"class":77,"line":78},[75,9750,2490],{"class":81},[75,9752,3847],{"class":171},[75,9754,605],{"class":81},[75,9756,8877],{"class":85},[75,9758,2536],{"class":190},[75,9760,9761],{"class":85},"(token, fs.",[75,9763,3000],{"class":190},[75,9765,2135],{"class":85},[75,9767,9768],{"class":92},"'./public.pem'",[75,9770,9771],{"class":85},"), { algorithms: [",[75,9773,9304],{"class":92},[75,9775,9307],{"class":85},[1745,9777,9778],{},"Never sees the private key, never touches refresh tokens.",[3960,9780,9781],{},[17,9782,9783],{},"Again: neither the access token nor the refresh token “is” the private key. The private key is solely used to sign the access token; the access token itself is just data + signature. The refresh token is just a server‐validated string (or long‐lived JWT) whose only job is to let you issue a new access token.",[58,9785,9787],{"id":9786},"_7-putting-it-into-code-nodejs-example-hs256","7. Putting It Into Code: Node.js Example (HS256)",[17,9789,9790],{},"The following is a minimal Express snippet showing:",[1742,9792,9793,9796,9799],{},[1745,9794,9795],{},"How to issue JWTs (HS256).",[1745,9797,9798],{},"How to store/validate refresh tokens in an in‐memory map (for demo only—use a real DB in production).",[1745,9800,9801],{},"How to rotate refresh tokens on use.",[3960,9803,9804],{},[17,9805,9806,9809],{},[1748,9807,9808],{},"IMPORTANT:"," Never use an in‐memory store for refresh tokens in production. Use a persistent store (Redis, SQL, etc.), so you can revoke tokens if your server restarts or if you need to blacklist a compromised token.",[66,9811,9813],{"className":2039,"code":9812,"language":2041,"meta":15,"style":15},"// File: authServer.js\nimport express from 'express';\nimport cookieParser from 'cookie-parser';\nimport jwt from 'jsonwebtoken';\nimport { randomBytes, createHash } from 'crypto';\n\nconst app = express();\napp.use(express.json());\napp.use(cookieParser());\n\nconst JWT_SECRET = process.env.JWT_SECRET || 'super-secure-secret-key'; // keep this safe\nconst ACCESS_TOKEN_EXPIRY = '15m';\nconst REFRESH_TOKEN_EXPIRY_SEC = 30 * 24 * 60 * 60; // 30 days in seconds\n\n// Simulated in‐memory store for refresh tokens:\n// key = hashedRefreshToken, value = { userId, expiresAt: unixTimestampSec }\nconst refreshTokenStore = new Map();\n\n// Utility to hash a token before storing in DB:\nfunction hashToken(token) {\n  return createHash('sha256').update(token).digest('hex');\n}\n\n// 1. LOGIN: Validate credentials, then issue both tokens\napp.post('/auth/login', async (req, res) => {\n  const { email, password } = req.body;\n  // (In production) verify email+password from your user database:\n  const user = await findUserByEmail(email);\n  if (!user || !checkPassword(user, password)) {\n    return res.status(401).json({ error: 'Invalid credentials' });\n  }\n\n  // 1a. Issue access token (short‐lived)\n  const accessToken = jwt.sign(\n    { sub: user.id, name: user.name, roles: user.roles },\n    JWT_SECRET,\n    { algorithm: 'HS256', expiresIn: ACCESS_TOKEN_EXPIRY }\n  );\n\n  // 1b. Issue a refresh token (random string), store its hash in DB\n  const rawRefreshToken = randomBytes(32).toString('hex'); // 256-bit\n  const hashed = hashToken(rawRefreshToken);\n  const expiresAt = Math.floor(Date.now() / 1000) + REFRESH_TOKEN_EXPIRY_SEC;\n\n  refreshTokenStore.set(hashed, { userId: user.id, expiresAt });\n\n  // 1c. Send both to the client (access in JSON; refresh as HttpOnly cookie)\n  res\n    .cookie('refreshToken', rawRefreshToken, {\n      httpOnly: true,\n      secure: process.env.NODE_ENV === 'production',\n      path: '/auth/refresh',\n      maxAge: REFRESH_TOKEN_EXPIRY_SEC * 1000\n    })\n    .json({ accessToken });\n});\n\n// 2. PROTECTED ROUTE: Verify Access Token\napp.get('/api/protected', (req, res) => {\n  const authHeader = req.headers['authorization'] || '';\n  const token = authHeader.split(' ')[1]; // “Bearer \u003Ctoken>”\n  if (!token) return res.sendStatus(401);\n\n  try {\n    const payload = jwt.verify(token, JWT_SECRET);\n    // payload = { sub: user.id, name: user.name, roles: [ … ], iat: \u003Cn>, exp: \u003Cn> }\n    req.user = { id: payload.sub, roles: payload.roles };\n    return res.json({ data: 'This is protected data', user: req.user });\n  } catch (err) {\n    return res.sendStatus(401);\n  }\n});\n\n// 3. REFRESH: Validate refresh token, rotate on use, issue new access token\napp.post('/auth/refresh', (req, res) => {\n  const rawRefreshToken = req.cookies.refreshToken;\n  if (!rawRefreshToken) {\n    return res.sendStatus(401);\n  }\n\n  const hashed = hashToken(rawRefreshToken);\n  const record = refreshTokenStore.get(hashed);\n\n  if (!record) {\n    return res.sendStatus(401); // no such token → unauthorized\n  }\n\n  const nowSec = Math.floor(Date.now() / 1000);\n  if (record.expiresAt \u003C nowSec) {\n    // expired refresh token\n    refreshTokenStore.delete(hashed);\n    return res.sendStatus(401);\n  }\n\n  // 3a. Rotate the refresh token:\n  refreshTokenStore.delete(hashed); // revoke old\n  const newRawRefresh = randomBytes(32).toString('hex');\n  const newHashed = hashToken(newRawRefresh);\n  const newExpiresAt = nowSec + REFRESH_TOKEN_EXPIRY_SEC;\n  refreshTokenStore.set(newHashed, { userId: record.userId, expiresAt: newExpiresAt });\n\n  // 3b. Issue a new access token:\n  const newAccessToken = jwt.sign(\n    { sub: record.userId /*, other claims… */ },\n    JWT_SECRET,\n    { algorithm: 'HS256', expiresIn: ACCESS_TOKEN_EXPIRY }\n  );\n\n  // 3c. Set new refresh token cookie, send new access token in JSON\n  res\n    .cookie('refreshToken', newRawRefresh, {\n      httpOnly: true,\n      secure: process.env.NODE_ENV === 'production',\n      path: '/auth/refresh',\n      maxAge: REFRESH_TOKEN_EXPIRY_SEC * 1000\n    })\n    .json({ accessToken: newAccessToken });\n});\n\n// 4. LOGOUT: Invalidate current refresh token\napp.post('/auth/logout', (req, res) => {\n  const rawRefreshToken = req.cookies.refreshToken;\n  if (rawRefreshToken) {\n    const hashed = hashToken(rawRefreshToken);\n    refreshTokenStore.delete(hashed);\n  }\n  // Clear cookie on client\n  res\n    .clearCookie('refreshToken', { path: '/auth/refresh' })\n    .sendStatus(204);\n});\n\napp.listen(4000, () => {\n  console.log('Auth server listening on port 4000');\n});\n\n/**\n * Dummy user‐lookup/password‐check functions for illustration only:\n */\nasync function findUserByEmail(email) {\n  // In reality, query your users table\n  if (email === 'alice@example.com') {\n    return { id: 'user123', name: 'Alice', roles: ['user'], passwordHash: '…' };\n  }\n  return null;\n}\nfunction checkPassword(user, password) {\n  // In reality, bcrypt.compare(password, user.passwordHash)\n  return password === 'correct‐horse‐battery‐staple';\n}\n\n### 8. Key Takeaways\n\n- Never confuse the refresh token with your signing key.\n- The signing key (HMAC secret or RSA/EC private key) stays on your server, never goes to a client, and is used purely to sign (and verify) JWTs.\n- A refresh token is just a long‐lived random credential that your server stores and checks; it has nothing to do with cryptographic signing of access tokens.\n- Access tokens are publicly visible to the client, but they are only valid because your server signed them with its private key/secret. The client does not hold any secret—only the signed JWT itself.\n- Refresh tokens are also “bearer” tokens, but they are never meant to be parsed or “verified” by the client. They simply live in the client’s HttpOnly cookie, and your server treats them as a key in a server‐side store (database/Redis) that says, “Yes, that client is allowed to get a new access token now.”\n- If you need asymmetric validation (e.g., microservices), use an RSA/EC key pair: sign with the private key on Auth Server; distribute only the public key to the resource servers to let them verify incoming JWTs. The refresh token stays opaque and is validated only by the Auth Server.\n- Do not send any “private key” to the browser. You do not want client code (or malicious third‐parties) to ever have the ability to sign their own tokens.\n\nBy following the above pattern, you ensure that:\n- No sensitive signing material ever leaves your backend.\n- Clients only hold a short‐lived access token and an opaque refresh token in an HttpOnly cookie.\n- Your refresh token never “becomes” a private key—it simply lets you decide when to issue a brand‐new signed access token.\n\n**TL;DR**\n\nIn a proper JWT flow, you must keep a single signing key (HMAC secret or RSA/EC private key) strictly on the server. You sign access tokens with that key and verify them with its counterpart (the same secret in HS256 or the public key in RS256). The refresh token is merely a long‐lived credential you store server‐side and never share except in an HttpOnly cookie. It should never act as a \"private key\" and is not used to cryptographically sign or verify access tokens.\n\n---\n\n## Part 2: Stateless Refresh-Token Flow\n\nThis section explains how and why you might build a truly stateless refresh‐token flow—along with the inherent trade‐offs. In short, avoiding server‐side state means giving up per‐token revocation and forced expiry. However, if those limitations are acceptable, a self‐contained (JWT) refresh token can live entirely in the client and be validated solely via signature and expiration time.\n\n### 1. What “Stateless Refresh Token” Actually Means\n\n#### Stateless (no server‐side store at all)\n\n* The server keeps zero records of which refresh tokens were issued.\n* Each refresh token must be fully “self‐contained”: it carries in its own payload everything the server needs to decide “OK, is this valid?”\n\nIn practice, that means the refresh token itself is a signed JWT (or JWS) that:\n\n* Encodes a unique user/session identifier (e.g. a user ID or “session ID”).\n* Carries an `exp` (expiration) claim.\n* (Optionally) carries any other claims you need (e.g. user roles, token version, etc.).\n\nWhen the client presents that refresh‐JWT, the server just:\n\n1. Verifies its signature against a server‐only signing key.\n2. Checks `exp` (and maybe `nbf`).\n3. If valid, issues a new access token (and optionally a new refresh token).\n\nNo database check—no “lookup” is done. Everything is in the JWT.\n\nBecause there’s no lookup (no hash‐table or DB), the server cannot:\n\n* Immediately revoke a single user’s existing refresh token (unless you switch to a new signing key or embed some revocation‐list version, which reintroduces state).\n* Force‐expire one client’s refresh token early; once it’s signed and unexpired, it’s valid until its JWT `exp`.\n* Detect “replay” of an old token (a stolen refresh JWT can be used until it expires).\n\n#### Why consider stateless at all?\n\n* You avoid any server‐side overhead for storing/reading refresh‐token records (e.g. no Redis or no SQL table).\n* Under very high load or simple microservices, you may want to keep your auth servers purely “sign/verify” without hitting a database on every refresh.\n* Fewer moving parts can mean easier horizontal scaling: any instance with the same signing key can handle a refresh request in one step.\n\n### 2. How to Build a Self-Contained (Stateless) Refresh Token in Node.js\n\nThe following is a minimal outline of a refresh flow that maintains zero state on the server. It uses JWTs (signed with HS256 or RS256), but any JWS-compatible algorithm can be substituted.\n\n#### 2.1 Decide on Your Signing Key\n\n**HS256 (HMAC):**\n\n```js\n// Example: put this in your environment (never check into Git)\nprocess.env.JWT_REFRESH_SECRET = 'a‐very‐long‐random‐string‐only‐on‐server';\n",[49,9814,9815,9820,9834,9848,9860,9873,9877,9891,9905,9918,9922,9946,9960,9989,9993,9998,10003,10019,10023,10028,10043,10069,10073,10077,10082,10113,10134,10139,10156,10179,10204,10208,10212,10217,10231,10236,10243,10257,10261,10265,10270,10298,10311,10346,10350,10361,10365,10370,10376,10391,10401,10418,10428,10442,10448,10457,10462,10467,10473,10501,10527,10559,10583,10588,10596,10617,10623,10633,10651,10661,10676,10681,10686,10691,10697,10722,10733,10745,10760,10765,10770,10783,10799,10804,10816,10834,10839,10844,10870,10883,10889,10900,10915,10920,10925,10931,10944,10970,10985,11004,11014,11019,11025,11040,11051,11058,11071,11076,11081,11087,11092,11106,11115,11128,11137,11148,11153,11162,11167,11172,11178,11204,11215,11223,11236,11245,11250,11256,11261,11281,11294,11299,11304,11324,11339,11344,11349,11355,11361,11367,11382,11388,11403,11434,11439,11449,11454,11473,11479,11494,11499,11504,11516,11521,11536,11570,11596,11620,11686,11726,11744,11749,11760,11768,11781,11794,11799,11817,11822,11928,11933,11939,11944,11960,11965,11996,12001,12012,12017,12029,12034,12047,12078,12083,12101,12106,12136,12150,12164,12169,12185,12190,12198,12218,12242,12247,12262,12267,12290,12295,12313,12328,12351,12356,12365,12370,12395,12408,12431,12436,12459,12464,12499,12504,12515,12520,12535,12540,12546,12552],{"__ignoreMap":15},[75,9816,9817],{"class":77,"line":78},[75,9818,9819],{"class":2542},"// File: authServer.js\n",[75,9821,9822,9824,9827,9829,9832],{"class":77,"line":25},[75,9823,2593],{"class":81},[75,9825,9826],{"class":85}," express ",[75,9828,2599],{"class":81},[75,9830,9831],{"class":92}," 'express'",[75,9833,2605],{"class":85},[75,9835,9836,9838,9841,9843,9846],{"class":77,"line":215},[75,9837,2593],{"class":81},[75,9839,9840],{"class":85}," cookieParser ",[75,9842,2599],{"class":81},[75,9844,9845],{"class":92}," 'cookie-parser'",[75,9847,2605],{"class":85},[75,9849,9850,9852,9854,9856,9858],{"class":77,"line":221},[75,9851,2593],{"class":81},[75,9853,8853],{"class":85},[75,9855,2599],{"class":81},[75,9857,8858],{"class":92},[75,9859,2605],{"class":85},[75,9861,9862,9864,9867,9869,9871],{"class":77,"line":235},[75,9863,2593],{"class":81},[75,9865,9866],{"class":85}," { randomBytes, createHash } ",[75,9868,2599],{"class":81},[75,9870,2602],{"class":92},[75,9872,2605],{"class":85},[75,9874,9875],{"class":77,"line":248},[75,9876,527],{"emptyLinePlaceholder":30},[75,9878,9879,9881,9884,9886,9889],{"class":77,"line":261},[75,9880,2490],{"class":81},[75,9882,9883],{"class":171}," app",[75,9885,605],{"class":81},[75,9887,9888],{"class":190}," express",[75,9890,2305],{"class":85},[75,9892,9893,9895,9897,9900,9902],{"class":77,"line":274},[75,9894,2048],{"class":85},[75,9896,2051],{"class":190},[75,9898,9899],{"class":85},"(express.",[75,9901,9122],{"class":190},[75,9903,9904],{"class":85},"());\n",[75,9906,9907,9909,9911,9913,9916],{"class":77,"line":615},[75,9908,2048],{"class":85},[75,9910,2051],{"class":190},[75,9912,2135],{"class":85},[75,9914,9915],{"class":190},"cookieParser",[75,9917,9904],{"class":85},[75,9919,9920],{"class":77,"line":636},[75,9921,527],{"emptyLinePlaceholder":30},[75,9923,9924,9926,9929,9931,9933,9935,9937,9940,9943],{"class":77,"line":642},[75,9925,2490],{"class":81},[75,9927,9928],{"class":171}," JWT_SECRET",[75,9930,605],{"class":81},[75,9932,6344],{"class":85},[75,9934,8894],{"class":171},[75,9936,200],{"class":81},[75,9938,9939],{"class":92}," 'super-secure-secret-key'",[75,9941,9942],{"class":85},"; ",[75,9944,9945],{"class":2542},"// keep this safe\n",[75,9947,9948,9950,9953,9955,9958],{"class":77,"line":663},[75,9949,2490],{"class":81},[75,9951,9952],{"class":171}," ACCESS_TOKEN_EXPIRY",[75,9954,605],{"class":81},[75,9956,9957],{"class":92}," '15m'",[75,9959,2605],{"class":85},[75,9961,9962,9964,9967,9969,9972,9974,9976,9978,9980,9982,9984,9986],{"class":77,"line":669},[75,9963,2490],{"class":81},[75,9965,9966],{"class":171}," REFRESH_TOKEN_EXPIRY_SEC",[75,9968,605],{"class":81},[75,9970,9971],{"class":171}," 30",[75,9973,9090],{"class":81},[75,9975,9093],{"class":171},[75,9977,9090],{"class":81},[75,9979,9098],{"class":171},[75,9981,9090],{"class":81},[75,9983,9098],{"class":171},[75,9985,9942],{"class":85},[75,9987,9988],{"class":2542},"// 30 days in seconds\n",[75,9990,9991],{"class":77,"line":675},[75,9992,527],{"emptyLinePlaceholder":30},[75,9994,9995],{"class":77,"line":684},[75,9996,9997],{"class":2542},"// Simulated in‐memory store for refresh tokens:\n",[75,9999,10000],{"class":77,"line":690},[75,10001,10002],{"class":2542},"// key = hashedRefreshToken, value = { userId, expiresAt: unixTimestampSec }\n",[75,10004,10005,10007,10010,10012,10014,10017],{"class":77,"line":2254},[75,10006,2490],{"class":81},[75,10008,10009],{"class":171}," refreshTokenStore",[75,10011,605],{"class":81},[75,10013,6372],{"class":81},[75,10015,10016],{"class":190}," Map",[75,10018,2305],{"class":85},[75,10020,10021],{"class":77,"line":2259},[75,10022,527],{"emptyLinePlaceholder":30},[75,10024,10025],{"class":77,"line":2276},[75,10026,10027],{"class":2542},"// Utility to hash a token before storing in DB:\n",[75,10029,10030,10033,10036,10038,10041],{"class":77,"line":2294},[75,10031,10032],{"class":81},"function",[75,10034,10035],{"class":190}," hashToken",[75,10037,2135],{"class":85},[75,10039,10040],{"class":501},"token",[75,10042,2273],{"class":85},[75,10044,10045,10047,10050,10052,10054,10056,10058,10061,10063,10065,10067],{"class":77,"line":2299},[75,10046,6598],{"class":81},[75,10048,10049],{"class":190}," createHash",[75,10051,2135],{"class":85},[75,10053,4806],{"class":92},[75,10055,2030],{"class":85},[75,10057,3358],{"class":190},[75,10059,10060],{"class":85},"(token).",[75,10062,5900],{"class":190},[75,10064,2135],{"class":85},[75,10066,3640],{"class":92},[75,10068,2160],{"class":85},[75,10070,10071],{"class":77,"line":2308},[75,10072,325],{"class":85},[75,10074,10075],{"class":77,"line":2792},[75,10076,527],{"emptyLinePlaceholder":30},[75,10078,10079],{"class":77,"line":2802},[75,10080,10081],{"class":2542},"// 1. LOGIN: Validate credentials, then issue both tokens\n",[75,10083,10084,10086,10089,10091,10094,10096,10099,10101,10103,10105,10107,10109,10111],{"class":77,"line":2810},[75,10085,2048],{"class":85},[75,10087,10088],{"class":190},"post",[75,10090,2135],{"class":85},[75,10092,10093],{"class":92},"'/auth/login'",[75,10095,1520],{"class":85},[75,10097,10098],{"class":81},"async",[75,10100,6358],{"class":85},[75,10102,2057],{"class":501},[75,10104,1520],{"class":85},[75,10106,2062],{"class":501},[75,10108,2070],{"class":85},[75,10110,2073],{"class":81},[75,10112,2076],{"class":85},[75,10114,10115,10117,10119,10122,10124,10127,10129,10131],{"class":77,"line":2815},[75,10116,2081],{"class":81},[75,10118,2621],{"class":85},[75,10120,10121],{"class":171},"email",[75,10123,1520],{"class":85},[75,10125,10126],{"class":171},"password",[75,10128,2632],{"class":85},[75,10130,89],{"class":81},[75,10132,10133],{"class":85}," req.body;\n",[75,10135,10136],{"class":77,"line":3092},[75,10137,10138],{"class":2542},"  // (In production) verify email+password from your user database:\n",[75,10140,10141,10143,10146,10148,10150,10153],{"class":77,"line":3103},[75,10142,2081],{"class":81},[75,10144,10145],{"class":171}," user",[75,10147,605],{"class":81},[75,10149,9436],{"class":81},[75,10151,10152],{"class":190}," findUserByEmail",[75,10154,10155],{"class":85},"(email);\n",[75,10157,10158,10160,10162,10164,10167,10170,10173,10176],{"class":77,"line":3114},[75,10159,532],{"class":81},[75,10161,6358],{"class":85},[75,10163,6361],{"class":81},[75,10165,10166],{"class":85},"user ",[75,10168,10169],{"class":81},"||",[75,10171,10172],{"class":81}," !",[75,10174,10175],{"class":190},"checkPassword",[75,10177,10178],{"class":85},"(user, password)) {\n",[75,10180,10181,10183,10185,10188,10190,10192,10194,10196,10199,10202],{"class":77,"line":3120},[75,10182,749],{"class":81},[75,10184,2281],{"class":85},[75,10186,10187],{"class":190},"status",[75,10189,2135],{"class":85},[75,10191,9240],{"class":171},[75,10193,2030],{"class":85},[75,10195,9122],{"class":190},[75,10197,10198],{"class":85},"({ error: ",[75,10200,10201],{"class":92},"'Invalid credentials'",[75,10203,3144],{"class":85},[75,10205,10206],{"class":77,"line":3126},[75,10207,2165],{"class":85},[75,10209,10210],{"class":77,"line":3147},[75,10211,527],{"emptyLinePlaceholder":30},[75,10213,10214],{"class":77,"line":5604},[75,10215,10216],{"class":2542},"  // 1a. Issue access token (short‐lived)\n",[75,10218,10219,10221,10223,10225,10227,10229],{"class":77,"line":5610},[75,10220,2081],{"class":81},[75,10222,8872],{"class":171},[75,10224,605],{"class":81},[75,10226,8877],{"class":85},[75,10228,2501],{"class":190},[75,10230,2197],{"class":85},[75,10232,10233],{"class":77,"line":5630},[75,10234,10235],{"class":85},"    { sub: user.id, name: user.name, roles: user.roles },\n",[75,10237,10238,10241],{"class":77,"line":5645},[75,10239,10240],{"class":171},"    JWT_SECRET",[75,10242,2205],{"class":85},[75,10244,10245,10248,10250,10252,10255],{"class":77,"line":5650},[75,10246,10247],{"class":85},"    { algorithm: ",[75,10249,8908],{"class":92},[75,10251,8911],{"class":85},[75,10253,10254],{"class":171},"ACCESS_TOKEN_EXPIRY",[75,10256,3791],{"class":85},[75,10258,10259],{"class":77,"line":5655},[75,10260,2215],{"class":85},[75,10262,10263],{"class":77,"line":5661},[75,10264,527],{"emptyLinePlaceholder":30},[75,10266,10267],{"class":77,"line":5682},[75,10268,10269],{"class":2542},"  // 1b. Issue a refresh token (random string), store its hash in DB\n",[75,10271,10272,10274,10277,10279,10281,10283,10285,10287,10289,10291,10293,10295],{"class":77,"line":5687},[75,10273,2081],{"class":81},[75,10275,10276],{"class":171}," rawRefreshToken",[75,10278,605],{"class":81},[75,10280,8958],{"class":190},[75,10282,2135],{"class":85},[75,10284,6456],{"class":171},[75,10286,2030],{"class":85},[75,10288,3467],{"class":190},[75,10290,2135],{"class":85},[75,10292,3640],{"class":92},[75,10294,3310],{"class":85},[75,10296,10297],{"class":2542},"// 256-bit\n",[75,10299,10300,10302,10304,10306,10308],{"class":77,"line":5704},[75,10301,2081],{"class":81},[75,10303,9416],{"class":171},[75,10305,605],{"class":81},[75,10307,10035],{"class":190},[75,10309,10310],{"class":85},"(rawRefreshToken);\n",[75,10312,10313,10315,10318,10320,10323,10326,10329,10331,10334,10336,10338,10340,10342,10344],{"class":77,"line":5710},[75,10314,2081],{"class":81},[75,10316,10317],{"class":171}," expiresAt",[75,10319,605],{"class":81},[75,10321,10322],{"class":85}," Math.",[75,10324,10325],{"class":190},"floor",[75,10327,10328],{"class":85},"(Date.",[75,10330,8795],{"class":190},[75,10332,10333],{"class":85},"() ",[75,10335,991],{"class":81},[75,10337,9107],{"class":171},[75,10339,2070],{"class":85},[75,10341,6612],{"class":81},[75,10343,9966],{"class":171},[75,10345,2605],{"class":85},[75,10347,10348],{"class":77,"line":5719},[75,10349,527],{"emptyLinePlaceholder":30},[75,10351,10352,10355,10358],{"class":77,"line":5726},[75,10353,10354],{"class":85},"  refreshTokenStore.",[75,10356,10357],{"class":190},"set",[75,10359,10360],{"class":85},"(hashed, { userId: user.id, expiresAt });\n",[75,10362,10363],{"class":77,"line":5732},[75,10364,527],{"emptyLinePlaceholder":30},[75,10366,10367],{"class":77,"line":5737},[75,10368,10369],{"class":2542},"  // 1c. Send both to the client (access in JSON; refresh as HttpOnly cookie)\n",[75,10371,10373],{"class":77,"line":10372},48,[75,10374,10375],{"class":85},"  res\n",[75,10377,10379,10382,10384,10386,10388],{"class":77,"line":10378},49,[75,10380,10381],{"class":85},"    .",[75,10383,9039],{"class":190},[75,10385,2135],{"class":85},[75,10387,9044],{"class":92},[75,10389,10390],{"class":85},", rawRefreshToken, {\n",[75,10392,10394,10397,10399],{"class":77,"line":10393},50,[75,10395,10396],{"class":85},"      httpOnly: ",[75,10398,1826],{"class":171},[75,10400,2205],{"class":85},[75,10402,10404,10407,10410,10413,10416],{"class":77,"line":10403},51,[75,10405,10406],{"class":85},"      secure: process.env.",[75,10408,10409],{"class":171},"NODE_ENV",[75,10411,10412],{"class":81}," ===",[75,10414,10415],{"class":92}," 'production'",[75,10417,2205],{"class":85},[75,10419,10421,10424,10426],{"class":77,"line":10420},52,[75,10422,10423],{"class":85},"      path: ",[75,10425,9077],{"class":92},[75,10427,2205],{"class":85},[75,10429,10431,10434,10437,10439],{"class":77,"line":10430},53,[75,10432,10433],{"class":85},"      maxAge: ",[75,10435,10436],{"class":171},"REFRESH_TOKEN_EXPIRY_SEC",[75,10438,9090],{"class":81},[75,10440,10441],{"class":171}," 1000\n",[75,10443,10445],{"class":77,"line":10444},54,[75,10446,10447],{"class":85},"    })\n",[75,10449,10451,10453,10455],{"class":77,"line":10450},55,[75,10452,10381],{"class":85},[75,10454,9122],{"class":190},[75,10456,9125],{"class":85},[75,10458,10460],{"class":77,"line":10459},56,[75,10461,2311],{"class":85},[75,10463,10465],{"class":77,"line":10464},57,[75,10466,527],{"emptyLinePlaceholder":30},[75,10468,10470],{"class":77,"line":10469},58,[75,10471,10472],{"class":2542},"// 2. PROTECTED ROUTE: Verify Access Token\n",[75,10474,10476,10478,10481,10483,10486,10489,10491,10493,10495,10497,10499],{"class":77,"line":10475},59,[75,10477,2048],{"class":85},[75,10479,10480],{"class":190},"get",[75,10482,2135],{"class":85},[75,10484,10485],{"class":92},"'/api/protected'",[75,10487,10488],{"class":85},", (",[75,10490,2057],{"class":501},[75,10492,1520],{"class":85},[75,10494,2062],{"class":501},[75,10496,2070],{"class":85},[75,10498,2073],{"class":81},[75,10500,2076],{"class":85},[75,10502,10504,10506,10509,10511,10514,10517,10520,10522,10525],{"class":77,"line":10503},60,[75,10505,2081],{"class":81},[75,10507,10508],{"class":171}," authHeader",[75,10510,605],{"class":81},[75,10512,10513],{"class":85}," req.headers[",[75,10515,10516],{"class":92},"'authorization'",[75,10518,10519],{"class":85},"] ",[75,10521,10169],{"class":81},[75,10523,10524],{"class":92}," ''",[75,10526,2605],{"class":85},[75,10528,10530,10532,10535,10537,10540,10543,10545,10548,10551,10553,10556],{"class":77,"line":10529},61,[75,10531,2081],{"class":81},[75,10533,10534],{"class":171}," token",[75,10536,605],{"class":81},[75,10538,10539],{"class":85}," authHeader.",[75,10541,10542],{"class":190},"split",[75,10544,2135],{"class":85},[75,10546,10547],{"class":92},"' '",[75,10549,10550],{"class":85},")[",[75,10552,7538],{"class":171},[75,10554,10555],{"class":85},"]; ",[75,10557,10558],{"class":2542},"// “Bearer \u003Ctoken>”\n",[75,10560,10562,10564,10566,10568,10571,10573,10575,10577,10579,10581],{"class":77,"line":10561},62,[75,10563,532],{"class":81},[75,10565,6358],{"class":85},[75,10567,6361],{"class":81},[75,10569,10570],{"class":85},"token) ",[75,10572,9560],{"class":81},[75,10574,2281],{"class":85},[75,10576,2284],{"class":190},[75,10578,2135],{"class":85},[75,10580,9240],{"class":171},[75,10582,2160],{"class":85},[75,10584,10586],{"class":77,"line":10585},63,[75,10587,527],{"emptyLinePlaceholder":30},[75,10589,10591,10594],{"class":77,"line":10590},64,[75,10592,10593],{"class":81},"  try",[75,10595,2076],{"class":85},[75,10597,10599,10602,10604,10606,10608,10610,10613,10615],{"class":77,"line":10598},65,[75,10600,10601],{"class":81},"    const",[75,10603,3847],{"class":171},[75,10605,605],{"class":81},[75,10607,8877],{"class":85},[75,10609,2536],{"class":190},[75,10611,10612],{"class":85},"(token, ",[75,10614,8894],{"class":171},[75,10616,2160],{"class":85},[75,10618,10620],{"class":77,"line":10619},66,[75,10621,10622],{"class":2542},"    // payload = { sub: user.id, name: user.name, roles: [ … ], iat: \u003Cn>, exp: \u003Cn> }\n",[75,10624,10626,10629,10631],{"class":77,"line":10625},67,[75,10627,10628],{"class":85},"    req.user ",[75,10630,89],{"class":81},[75,10632,9207],{"class":85},[75,10634,10636,10638,10640,10642,10645,10648],{"class":77,"line":10635},68,[75,10637,749],{"class":81},[75,10639,2281],{"class":85},[75,10641,9122],{"class":190},[75,10643,10644],{"class":85},"({ data: ",[75,10646,10647],{"class":92},"'This is protected data'",[75,10649,10650],{"class":85},", user: req.user });\n",[75,10652,10654,10657,10659],{"class":77,"line":10653},69,[75,10655,10656],{"class":85},"  } ",[75,10658,9221],{"class":81},[75,10660,9224],{"class":85},[75,10662,10664,10666,10668,10670,10672,10674],{"class":77,"line":10663},70,[75,10665,749],{"class":81},[75,10667,2281],{"class":85},[75,10669,2284],{"class":190},[75,10671,2135],{"class":85},[75,10673,9240],{"class":171},[75,10675,2160],{"class":85},[75,10677,10679],{"class":77,"line":10678},71,[75,10680,2165],{"class":85},[75,10682,10684],{"class":77,"line":10683},72,[75,10685,2311],{"class":85},[75,10687,10689],{"class":77,"line":10688},73,[75,10690,527],{"emptyLinePlaceholder":30},[75,10692,10694],{"class":77,"line":10693},74,[75,10695,10696],{"class":2542},"// 3. REFRESH: Validate refresh token, rotate on use, issue new access token\n",[75,10698,10700,10702,10704,10706,10708,10710,10712,10714,10716,10718,10720],{"class":77,"line":10699},75,[75,10701,2048],{"class":85},[75,10703,10088],{"class":190},[75,10705,2135],{"class":85},[75,10707,9077],{"class":92},[75,10709,10488],{"class":85},[75,10711,2057],{"class":501},[75,10713,1520],{"class":85},[75,10715,2062],{"class":501},[75,10717,2070],{"class":85},[75,10719,2073],{"class":81},[75,10721,2076],{"class":85},[75,10723,10725,10727,10729,10731],{"class":77,"line":10724},76,[75,10726,2081],{"class":81},[75,10728,10276],{"class":171},[75,10730,605],{"class":81},[75,10732,9393],{"class":85},[75,10734,10736,10738,10740,10742],{"class":77,"line":10735},77,[75,10737,532],{"class":81},[75,10739,6358],{"class":85},[75,10741,6361],{"class":81},[75,10743,10744],{"class":85},"rawRefreshToken) {\n",[75,10746,10748,10750,10752,10754,10756,10758],{"class":77,"line":10747},78,[75,10749,749],{"class":81},[75,10751,2281],{"class":85},[75,10753,2284],{"class":190},[75,10755,2135],{"class":85},[75,10757,9240],{"class":171},[75,10759,2160],{"class":85},[75,10761,10763],{"class":77,"line":10762},79,[75,10764,2165],{"class":85},[75,10766,10768],{"class":77,"line":10767},80,[75,10769,527],{"emptyLinePlaceholder":30},[75,10771,10773,10775,10777,10779,10781],{"class":77,"line":10772},81,[75,10774,2081],{"class":81},[75,10776,9416],{"class":171},[75,10778,605],{"class":81},[75,10780,10035],{"class":190},[75,10782,10310],{"class":85},[75,10784,10786,10788,10790,10792,10795,10797],{"class":77,"line":10785},82,[75,10787,2081],{"class":81},[75,10789,9431],{"class":171},[75,10791,605],{"class":81},[75,10793,10794],{"class":85}," refreshTokenStore.",[75,10796,10480],{"class":190},[75,10798,9445],{"class":85},[75,10800,10802],{"class":77,"line":10801},83,[75,10803,527],{"emptyLinePlaceholder":30},[75,10805,10807,10809,10811,10813],{"class":77,"line":10806},84,[75,10808,532],{"class":81},[75,10810,6358],{"class":85},[75,10812,6361],{"class":81},[75,10814,10815],{"class":85},"record) {\n",[75,10817,10819,10821,10823,10825,10827,10829,10831],{"class":77,"line":10818},85,[75,10820,749],{"class":81},[75,10822,2281],{"class":85},[75,10824,2284],{"class":190},[75,10826,2135],{"class":85},[75,10828,9240],{"class":171},[75,10830,3310],{"class":85},[75,10832,10833],{"class":2542},"// no such token → unauthorized\n",[75,10835,10837],{"class":77,"line":10836},86,[75,10838,2165],{"class":85},[75,10840,10842],{"class":77,"line":10841},87,[75,10843,527],{"emptyLinePlaceholder":30},[75,10845,10847,10849,10852,10854,10856,10858,10860,10862,10864,10866,10868],{"class":77,"line":10846},88,[75,10848,2081],{"class":81},[75,10850,10851],{"class":171}," nowSec",[75,10853,605],{"class":81},[75,10855,10322],{"class":85},[75,10857,10325],{"class":190},[75,10859,10328],{"class":85},[75,10861,8795],{"class":190},[75,10863,10333],{"class":85},[75,10865,991],{"class":81},[75,10867,9107],{"class":171},[75,10869,2160],{"class":85},[75,10871,10873,10875,10878,10880],{"class":77,"line":10872},89,[75,10874,532],{"class":81},[75,10876,10877],{"class":85}," (record.expiresAt ",[75,10879,7163],{"class":81},[75,10881,10882],{"class":85}," nowSec) {\n",[75,10884,10886],{"class":77,"line":10885},90,[75,10887,10888],{"class":2542},"    // expired refresh token\n",[75,10890,10892,10895,10898],{"class":77,"line":10891},91,[75,10893,10894],{"class":85},"    refreshTokenStore.",[75,10896,10897],{"class":190},"delete",[75,10899,9445],{"class":85},[75,10901,10903,10905,10907,10909,10911,10913],{"class":77,"line":10902},92,[75,10904,749],{"class":81},[75,10906,2281],{"class":85},[75,10908,2284],{"class":190},[75,10910,2135],{"class":85},[75,10912,9240],{"class":171},[75,10914,2160],{"class":85},[75,10916,10918],{"class":77,"line":10917},93,[75,10919,2165],{"class":85},[75,10921,10923],{"class":77,"line":10922},94,[75,10924,527],{"emptyLinePlaceholder":30},[75,10926,10928],{"class":77,"line":10927},95,[75,10929,10930],{"class":2542},"  // 3a. Rotate the refresh token:\n",[75,10932,10934,10936,10938,10941],{"class":77,"line":10933},96,[75,10935,10354],{"class":85},[75,10937,10897],{"class":190},[75,10939,10940],{"class":85},"(hashed); ",[75,10942,10943],{"class":2542},"// revoke old\n",[75,10945,10947,10949,10952,10954,10956,10958,10960,10962,10964,10966,10968],{"class":77,"line":10946},97,[75,10948,2081],{"class":81},[75,10950,10951],{"class":171}," newRawRefresh",[75,10953,605],{"class":81},[75,10955,8958],{"class":190},[75,10957,2135],{"class":85},[75,10959,6456],{"class":171},[75,10961,2030],{"class":85},[75,10963,3467],{"class":190},[75,10965,2135],{"class":85},[75,10967,3640],{"class":92},[75,10969,2160],{"class":85},[75,10971,10973,10975,10978,10980,10982],{"class":77,"line":10972},98,[75,10974,2081],{"class":81},[75,10976,10977],{"class":171}," newHashed",[75,10979,605],{"class":81},[75,10981,10035],{"class":190},[75,10983,10984],{"class":85},"(newRawRefresh);\n",[75,10986,10988,10990,10993,10995,10998,11000,11002],{"class":77,"line":10987},99,[75,10989,2081],{"class":81},[75,10991,10992],{"class":171}," newExpiresAt",[75,10994,605],{"class":81},[75,10996,10997],{"class":85}," nowSec ",[75,10999,6612],{"class":81},[75,11001,9966],{"class":171},[75,11003,2605],{"class":85},[75,11005,11007,11009,11011],{"class":77,"line":11006},100,[75,11008,10354],{"class":85},[75,11010,10357],{"class":190},[75,11012,11013],{"class":85},"(newHashed, { userId: record.userId, expiresAt: newExpiresAt });\n",[75,11015,11017],{"class":77,"line":11016},101,[75,11018,527],{"emptyLinePlaceholder":30},[75,11020,11022],{"class":77,"line":11021},102,[75,11023,11024],{"class":2542},"  // 3b. Issue a new access token:\n",[75,11026,11028,11030,11032,11034,11036,11038],{"class":77,"line":11027},103,[75,11029,2081],{"class":81},[75,11031,9485],{"class":171},[75,11033,605],{"class":81},[75,11035,8877],{"class":85},[75,11037,2501],{"class":190},[75,11039,2197],{"class":85},[75,11041,11043,11046,11049],{"class":77,"line":11042},104,[75,11044,11045],{"class":85},"    { sub: record.userId ",[75,11047,11048],{"class":2542},"/*, other claims… */",[75,11050,3776],{"class":85},[75,11052,11054,11056],{"class":77,"line":11053},105,[75,11055,10240],{"class":171},[75,11057,2205],{"class":85},[75,11059,11061,11063,11065,11067,11069],{"class":77,"line":11060},106,[75,11062,10247],{"class":85},[75,11064,8908],{"class":92},[75,11066,8911],{"class":85},[75,11068,10254],{"class":171},[75,11070,3791],{"class":85},[75,11072,11074],{"class":77,"line":11073},107,[75,11075,2215],{"class":85},[75,11077,11079],{"class":77,"line":11078},108,[75,11080,527],{"emptyLinePlaceholder":30},[75,11082,11084],{"class":77,"line":11083},109,[75,11085,11086],{"class":2542},"  // 3c. Set new refresh token cookie, send new access token in JSON\n",[75,11088,11090],{"class":77,"line":11089},110,[75,11091,10375],{"class":85},[75,11093,11095,11097,11099,11101,11103],{"class":77,"line":11094},111,[75,11096,10381],{"class":85},[75,11098,9039],{"class":190},[75,11100,2135],{"class":85},[75,11102,9044],{"class":92},[75,11104,11105],{"class":85},", newRawRefresh, {\n",[75,11107,11109,11111,11113],{"class":77,"line":11108},112,[75,11110,10396],{"class":85},[75,11112,1826],{"class":171},[75,11114,2205],{"class":85},[75,11116,11118,11120,11122,11124,11126],{"class":77,"line":11117},113,[75,11119,10406],{"class":85},[75,11121,10409],{"class":171},[75,11123,10412],{"class":81},[75,11125,10415],{"class":92},[75,11127,2205],{"class":85},[75,11129,11131,11133,11135],{"class":77,"line":11130},114,[75,11132,10423],{"class":85},[75,11134,9077],{"class":92},[75,11136,2205],{"class":85},[75,11138,11140,11142,11144,11146],{"class":77,"line":11139},115,[75,11141,10433],{"class":85},[75,11143,10436],{"class":171},[75,11145,9090],{"class":81},[75,11147,10441],{"class":171},[75,11149,11151],{"class":77,"line":11150},116,[75,11152,10447],{"class":85},[75,11154,11156,11158,11160],{"class":77,"line":11155},117,[75,11157,10381],{"class":85},[75,11159,9122],{"class":190},[75,11161,9567],{"class":85},[75,11163,11165],{"class":77,"line":11164},118,[75,11166,2311],{"class":85},[75,11168,11170],{"class":77,"line":11169},119,[75,11171,527],{"emptyLinePlaceholder":30},[75,11173,11175],{"class":77,"line":11174},120,[75,11176,11177],{"class":2542},"// 4. LOGOUT: Invalidate current refresh token\n",[75,11179,11181,11183,11185,11187,11190,11192,11194,11196,11198,11200,11202],{"class":77,"line":11180},121,[75,11182,2048],{"class":85},[75,11184,10088],{"class":190},[75,11186,2135],{"class":85},[75,11188,11189],{"class":92},"'/auth/logout'",[75,11191,10488],{"class":85},[75,11193,2057],{"class":501},[75,11195,1520],{"class":85},[75,11197,2062],{"class":501},[75,11199,2070],{"class":85},[75,11201,2073],{"class":81},[75,11203,2076],{"class":85},[75,11205,11207,11209,11211,11213],{"class":77,"line":11206},122,[75,11208,2081],{"class":81},[75,11210,10276],{"class":171},[75,11212,605],{"class":81},[75,11214,9393],{"class":85},[75,11216,11218,11220],{"class":77,"line":11217},123,[75,11219,532],{"class":81},[75,11221,11222],{"class":85}," (rawRefreshToken) {\n",[75,11224,11226,11228,11230,11232,11234],{"class":77,"line":11225},124,[75,11227,10601],{"class":81},[75,11229,9416],{"class":171},[75,11231,605],{"class":81},[75,11233,10035],{"class":190},[75,11235,10310],{"class":85},[75,11237,11239,11241,11243],{"class":77,"line":11238},125,[75,11240,10894],{"class":85},[75,11242,10897],{"class":190},[75,11244,9445],{"class":85},[75,11246,11248],{"class":77,"line":11247},126,[75,11249,2165],{"class":85},[75,11251,11253],{"class":77,"line":11252},127,[75,11254,11255],{"class":2542},"  // Clear cookie on client\n",[75,11257,11259],{"class":77,"line":11258},128,[75,11260,10375],{"class":85},[75,11262,11264,11266,11269,11271,11273,11276,11278],{"class":77,"line":11263},129,[75,11265,10381],{"class":85},[75,11267,11268],{"class":190},"clearCookie",[75,11270,2135],{"class":85},[75,11272,9044],{"class":92},[75,11274,11275],{"class":85},", { path: ",[75,11277,9077],{"class":92},[75,11279,11280],{"class":85}," })\n",[75,11282,11284,11286,11288,11290,11292],{"class":77,"line":11283},130,[75,11285,10381],{"class":85},[75,11287,2284],{"class":190},[75,11289,2135],{"class":85},[75,11291,2289],{"class":171},[75,11293,2160],{"class":85},[75,11295,11297],{"class":77,"line":11296},131,[75,11298,2311],{"class":85},[75,11300,11302],{"class":77,"line":11301},132,[75,11303,527],{"emptyLinePlaceholder":30},[75,11305,11307,11309,11312,11314,11317,11320,11322],{"class":77,"line":11306},133,[75,11308,2048],{"class":85},[75,11310,11311],{"class":190},"listen",[75,11313,2135],{"class":85},[75,11315,11316],{"class":171},"4000",[75,11318,11319],{"class":85},", () ",[75,11321,2073],{"class":81},[75,11323,2076],{"class":85},[75,11325,11327,11330,11332,11334,11337],{"class":77,"line":11326},134,[75,11328,11329],{"class":85},"  console.",[75,11331,3456],{"class":190},[75,11333,2135],{"class":85},[75,11335,11336],{"class":92},"'Auth server listening on port 4000'",[75,11338,2160],{"class":85},[75,11340,11342],{"class":77,"line":11341},135,[75,11343,2311],{"class":85},[75,11345,11347],{"class":77,"line":11346},136,[75,11348,527],{"emptyLinePlaceholder":30},[75,11350,11352],{"class":77,"line":11351},137,[75,11353,11354],{"class":2542},"/**\n",[75,11356,11358],{"class":77,"line":11357},138,[75,11359,11360],{"class":2542}," * Dummy user‐lookup/password‐check functions for illustration only:\n",[75,11362,11364],{"class":77,"line":11363},139,[75,11365,11366],{"class":2542}," */\n",[75,11368,11370,11372,11374,11376,11378,11380],{"class":77,"line":11369},140,[75,11371,10098],{"class":81},[75,11373,6496],{"class":81},[75,11375,10152],{"class":190},[75,11377,2135],{"class":85},[75,11379,10121],{"class":501},[75,11381,2273],{"class":85},[75,11383,11385],{"class":77,"line":11384},141,[75,11386,11387],{"class":2542},"  // In reality, query your users table\n",[75,11389,11391,11393,11396,11398,11401],{"class":77,"line":11390},142,[75,11392,532],{"class":81},[75,11394,11395],{"class":85}," (email ",[75,11397,2267],{"class":81},[75,11399,11400],{"class":92}," 'alice@example.com'",[75,11402,2273],{"class":85},[75,11404,11406,11408,11411,11414,11417,11420,11423,11426,11429,11431],{"class":77,"line":11405},143,[75,11407,749],{"class":81},[75,11409,11410],{"class":85}," { id: ",[75,11412,11413],{"class":92},"'user123'",[75,11415,11416],{"class":85},", name: ",[75,11418,11419],{"class":92},"'Alice'",[75,11421,11422],{"class":85},", roles: [",[75,11424,11425],{"class":92},"'user'",[75,11427,11428],{"class":85},"], passwordHash: ",[75,11430,6030],{"class":92},[75,11432,11433],{"class":85}," };\n",[75,11435,11437],{"class":77,"line":11436},144,[75,11438,2165],{"class":85},[75,11440,11442,11444,11447],{"class":77,"line":11441},145,[75,11443,6598],{"class":81},[75,11445,11446],{"class":171}," null",[75,11448,2605],{"class":85},[75,11450,11452],{"class":77,"line":11451},146,[75,11453,325],{"class":85},[75,11455,11457,11459,11462,11464,11467,11469,11471],{"class":77,"line":11456},147,[75,11458,10032],{"class":81},[75,11460,11461],{"class":190}," checkPassword",[75,11463,2135],{"class":85},[75,11465,11466],{"class":501},"user",[75,11468,1520],{"class":85},[75,11470,10126],{"class":501},[75,11472,2273],{"class":85},[75,11474,11476],{"class":77,"line":11475},148,[75,11477,11478],{"class":2542},"  // In reality, bcrypt.compare(password, user.passwordHash)\n",[75,11480,11482,11484,11487,11489,11492],{"class":77,"line":11481},149,[75,11483,6598],{"class":81},[75,11485,11486],{"class":85}," password ",[75,11488,2267],{"class":81},[75,11490,11491],{"class":92}," 'correct‐horse‐battery‐staple'",[75,11493,2605],{"class":85},[75,11495,11497],{"class":77,"line":11496},150,[75,11498,325],{"class":85},[75,11500,11502],{"class":77,"line":11501},151,[75,11503,527],{"emptyLinePlaceholder":30},[75,11505,11507,11510,11513],{"class":77,"line":11506},152,[75,11508,11509],{"class":85},"### ",[75,11511,11512],{"class":171},"8.",[75,11514,11515],{"class":85}," Key Takeaways\n",[75,11517,11519],{"class":77,"line":11518},153,[75,11520,527],{"emptyLinePlaceholder":30},[75,11522,11524,11527,11530,11533],{"class":77,"line":11523},154,[75,11525,11526],{"class":81},"-",[75,11528,11529],{"class":85}," Never confuse the refresh token ",[75,11531,11532],{"class":81},"with",[75,11534,11535],{"class":85}," your signing key.\n",[75,11537,11539,11541,11544,11546,11548,11551,11554,11557,11559,11562,11565,11567],{"class":77,"line":11538},155,[75,11540,11526],{"class":81},[75,11542,11543],{"class":85}," The signing ",[75,11545,3178],{"class":190},[75,11547,6358],{"class":85},[75,11549,11550],{"class":171},"HMAC",[75,11552,11553],{"class":85}," secret or ",[75,11555,11556],{"class":171},"RSA",[75,11558,991],{"class":81},[75,11560,11561],{"class":171},"EC",[75,11563,11564],{"class":85}," private key) stays on your server, never goes to a client, and is used purely to ",[75,11566,2501],{"class":190},[75,11568,11569],{"class":85}," (and verify) JWTs.\n",[75,11571,11573,11575,11578,11581,11584,11587,11590,11593],{"class":77,"line":11572},156,[75,11574,11526],{"class":81},[75,11576,11577],{"class":171}," A",[75,11579,11580],{"class":85}," refresh token is just a long‐lived random credential that your server stores and checks; it has nothing to ",[75,11582,11583],{"class":81},"do",[75,11585,11586],{"class":81}," with",[75,11588,11589],{"class":85}," cryptographic signing ",[75,11591,11592],{"class":81},"of",[75,11594,11595],{"class":85}," access tokens.\n",[75,11597,11599,11601,11604,11606,11609,11611,11614,11617],{"class":77,"line":11598},157,[75,11600,11526],{"class":81},[75,11602,11603],{"class":85}," Access tokens are publicly visible to the client, but they are only valid because your server signed them ",[75,11605,11532],{"class":81},[75,11607,11608],{"class":85}," its private key",[75,11610,991],{"class":81},[75,11612,11613],{"class":85},"secret. The client does not hold any secret—only the signed ",[75,11615,11616],{"class":171},"JWT",[75,11618,11619],{"class":85}," itself.\n",[75,11621,11623,11625,11628,11631,11634,11637,11640,11642,11645,11647,11650,11653,11656,11659,11661,11664,11666,11669,11671,11674,11677,11680,11683],{"class":77,"line":11622},158,[75,11624,11526],{"class":81},[75,11626,11627],{"class":85}," Refresh tokens are also “bearer” tokens, but they are never meant to be parsed or “verified” by the client. They simply live ",[75,11629,11630],{"class":81},"in",[75,11632,11633],{"class":85}," the client’s HttpOnly cookie, and your server treats them ",[75,11635,11636],{"class":81},"as",[75,11638,11639],{"class":190}," a",[75,11641,6397],{"class":190},[75,11643,11644],{"class":190}," in",[75,11646,11639],{"class":190},[75,11648,11649],{"class":190}," server",[75,11651,11652],{"class":85},"‐",[75,11654,11655],{"class":190},"side",[75,11657,11658],{"class":190}," store",[75,11660,6358],{"class":85},[75,11662,11663],{"class":190},"database",[75,11665,991],{"class":85},[75,11667,11668],{"class":190},"Redis",[75,11670,2070],{"class":85},[75,11672,11673],{"class":190},"that",[75,11675,11676],{"class":190}," says",[75,11678,11679],{"class":85},", “Yes, that client is allowed to get a ",[75,11681,11682],{"class":81},"new",[75,11684,11685],{"class":85}," access token now.”\n",[75,11687,11689,11691,11694,11697,11700,11702,11704,11706,11709,11712,11715,11717,11720,11723],{"class":77,"line":11688},159,[75,11690,11526],{"class":81},[75,11692,11693],{"class":85}," If you need asymmetric ",[75,11695,11696],{"class":190},"validation",[75,11698,11699],{"class":85}," (e.g., microservices), use an ",[75,11701,11556],{"class":171},[75,11703,991],{"class":81},[75,11705,11561],{"class":171},[75,11707,11708],{"class":85}," key ",[75,11710,11711],{"class":190},"pair",[75,11713,11714],{"class":85},": sign ",[75,11716,11532],{"class":81},[75,11718,11719],{"class":85}," the private key on Auth Server; distribute only the public key to the resource servers to ",[75,11721,11722],{"class":81},"let",[75,11724,11725],{"class":85}," them verify incoming JWTs. The refresh token stays opaque and is validated only by the Auth Server.\n",[75,11727,11729,11731,11734,11736,11739,11741],{"class":77,"line":11728},160,[75,11730,11526],{"class":81},[75,11732,11733],{"class":85}," Do not send any “private key” to the browser. You ",[75,11735,11583],{"class":81},[75,11737,11738],{"class":85}," not want client ",[75,11740,49],{"class":190},[75,11742,11743],{"class":85}," (or malicious third‐parties) to ever have the ability to sign their own tokens.\n",[75,11745,11747],{"class":77,"line":11746},161,[75,11748,527],{"emptyLinePlaceholder":30},[75,11750,11752,11755,11757],{"class":77,"line":11751},162,[75,11753,11754],{"class":85},"By following the above pattern, you ensure ",[75,11756,11673],{"class":190},[75,11758,11759],{"class":85},":\n",[75,11761,11763,11765],{"class":77,"line":11762},163,[75,11764,11526],{"class":81},[75,11766,11767],{"class":85}," No sensitive signing material ever leaves your backend.\n",[75,11769,11771,11773,11776,11778],{"class":77,"line":11770},164,[75,11772,11526],{"class":81},[75,11774,11775],{"class":85}," Clients only hold a short‐lived access token and an opaque refresh token ",[75,11777,11630],{"class":81},[75,11779,11780],{"class":85}," an HttpOnly cookie.\n",[75,11782,11784,11786,11789,11791],{"class":77,"line":11783},165,[75,11785,11526],{"class":81},[75,11787,11788],{"class":85}," Your refresh token never “becomes” a private key—it simply lets you decide when to issue a brand‐",[75,11790,11682],{"class":81},[75,11792,11793],{"class":85}," signed access token.\n",[75,11795,11797],{"class":77,"line":11796},166,[75,11798,527],{"emptyLinePlaceholder":30},[75,11800,11802,11805,11808,11811,11814],{"class":77,"line":11801},167,[75,11803,11804],{"class":81},"**",[75,11806,11807],{"class":171},"TL",[75,11809,11810],{"class":85},";",[75,11812,11813],{"class":171},"DR",[75,11815,11816],{"class":81},"**\n",[75,11818,11820],{"class":77,"line":11819},168,[75,11821,527],{"emptyLinePlaceholder":30},[75,11823,11825,11828,11830,11833,11835,11837,11839,11841,11843,11845,11847,11850,11852,11855,11857,11860,11863,11866,11868,11871,11874,11876,11879,11882,11884,11887,11889,11891,11894,11897,11900,11903,11906,11909,11912,11914,11917,11919,11922,11925],{"class":77,"line":11824},169,[75,11826,11827],{"class":85},"In a proper ",[75,11829,11616],{"class":171},[75,11831,11832],{"class":85}," flow, you must keep a single signing ",[75,11834,3178],{"class":190},[75,11836,6358],{"class":85},[75,11838,11550],{"class":171},[75,11840,11553],{"class":85},[75,11842,11556],{"class":171},[75,11844,991],{"class":81},[75,11846,11561],{"class":171},[75,11848,11849],{"class":85}," private key) strictly on the server. You sign access tokens ",[75,11851,11532],{"class":81},[75,11853,11854],{"class":85}," that key and verify them ",[75,11856,11532],{"class":81},[75,11858,11859],{"class":85}," its ",[75,11861,11862],{"class":190},"counterpart",[75,11864,11865],{"class":85}," (the same secret ",[75,11867,11630],{"class":81},[75,11869,11870],{"class":171}," HS256",[75,11872,11873],{"class":85}," or the public key ",[75,11875,11630],{"class":81},[75,11877,11878],{"class":171}," RS256",[75,11880,11881],{"class":85},"). The refresh token is merely a long‐lived credential you store server‐side and never share except ",[75,11883,11630],{"class":81},[75,11885,11886],{"class":85}," an HttpOnly cookie. It should never act ",[75,11888,11636],{"class":81},[75,11890,11639],{"class":190},[75,11892,11893],{"class":92}," \"private key\"",[75,11895,11896],{"class":190}," and",[75,11898,11899],{"class":190}," is",[75,11901,11902],{"class":190}," not",[75,11904,11905],{"class":190}," used",[75,11907,11908],{"class":190}," to",[75,11910,11911],{"class":190}," cryptographically",[75,11913,3329],{"class":190},[75,11915,11916],{"class":190}," or",[75,11918,4015],{"class":190},[75,11920,11921],{"class":190}," access",[75,11923,11924],{"class":190}," tokens",[75,11926,11927],{"class":85},".\n",[75,11929,11931],{"class":77,"line":11930},170,[75,11932,527],{"emptyLinePlaceholder":30},[75,11934,11936],{"class":77,"line":11935},171,[75,11937,11938],{"class":81},"---\n",[75,11940,11942],{"class":77,"line":11941},172,[75,11943,527],{"emptyLinePlaceholder":30},[75,11945,11947,11950,11952,11955,11957],{"class":77,"line":11946},173,[75,11948,11949],{"class":85},"## Part ",[75,11951,7749],{"class":171},[75,11953,11954],{"class":85},": Stateless Refresh",[75,11956,11526],{"class":81},[75,11958,11959],{"class":85},"Token Flow\n",[75,11961,11963],{"class":77,"line":11962},174,[75,11964,527],{"emptyLinePlaceholder":30},[75,11966,11968,11971,11973,11976,11978,11981,11984,11986,11988,11991,11993],{"class":77,"line":11967},175,[75,11969,11970],{"class":85},"This section explains how and why you might build a truly stateless refresh‐token flow—along ",[75,11972,11532],{"class":81},[75,11974,11975],{"class":85}," the inherent trade‐offs. In short, avoiding server‐side state means giving up per‐token revocation and forced expiry. However, ",[75,11977,154],{"class":81},[75,11979,11980],{"class":85}," those limitations are acceptable, a self‐",[75,11982,11983],{"class":190},"contained",[75,11985,6358],{"class":85},[75,11987,11616],{"class":171},[75,11989,11990],{"class":85},") refresh token can live entirely ",[75,11992,11630],{"class":81},[75,11994,11995],{"class":85}," the client and be validated solely via signature and expiration time.\n",[75,11997,11999],{"class":77,"line":11998},176,[75,12000,527],{"emptyLinePlaceholder":30},[75,12002,12004,12006,12009],{"class":77,"line":12003},177,[75,12005,11509],{"class":85},[75,12007,12008],{"class":171},"1.",[75,12010,12011],{"class":85}," What “Stateless Refresh Token” Actually Means\n",[75,12013,12015],{"class":77,"line":12014},178,[75,12016,527],{"emptyLinePlaceholder":30},[75,12018,12020,12023,12026],{"class":77,"line":12019},179,[75,12021,12022],{"class":85},"#### ",[75,12024,12025],{"class":190},"Stateless",[75,12027,12028],{"class":85}," (no server‐side store at all)\n",[75,12030,12032],{"class":77,"line":12031},180,[75,12033,527],{"emptyLinePlaceholder":30},[75,12035,12037,12039,12042,12044],{"class":77,"line":12036},181,[75,12038,995],{"class":81},[75,12040,12041],{"class":85}," The server keeps zero records ",[75,12043,11592],{"class":81},[75,12045,12046],{"class":85}," which refresh tokens were issued.\n",[75,12048,12050,12052,12055,12057,12060,12063,12066,12069,12072,12075],{"class":77,"line":12049},182,[75,12051,995],{"class":81},[75,12053,12054],{"class":85}," Each refresh token must be fully “self‐contained”: it carries ",[75,12056,11630],{"class":81},[75,12058,12059],{"class":85}," its own payload everything the server needs to decide “",[75,12061,12062],{"class":171},"OK",[75,12064,12065],{"class":85},", is ",[75,12067,12068],{"class":171},"this",[75,12070,12071],{"class":85}," valid",[75,12073,12074],{"class":81},"?",[75,12076,12077],{"class":85},"”\n",[75,12079,12081],{"class":77,"line":12080},183,[75,12082,527],{"emptyLinePlaceholder":30},[75,12084,12086,12089,12091,12093,12096,12099],{"class":77,"line":12085},184,[75,12087,12088],{"class":85},"In practice, that means the refresh token itself is a signed ",[75,12090,11616],{"class":190},[75,12092,3497],{"class":85},[75,12094,12095],{"class":171},"JWS",[75,12097,12098],{"class":85},") that",[75,12100,11759],{"class":81},[75,12102,12104],{"class":77,"line":12103},185,[75,12105,527],{"emptyLinePlaceholder":30},[75,12107,12109,12111,12114,12116,12119,12122,12125,12128,12131,12133],{"class":77,"line":12108},186,[75,12110,995],{"class":81},[75,12112,12113],{"class":85}," Encodes a unique user",[75,12115,991],{"class":81},[75,12117,12118],{"class":85},"session ",[75,12120,12121],{"class":190},"identifier",[75,12123,12124],{"class":85}," (e.g. a user ",[75,12126,12127],{"class":171},"ID",[75,12129,12130],{"class":85}," or “session ",[75,12132,12127],{"class":171},[75,12134,12135],{"class":85},"”).\n",[75,12137,12139,12141,12144,12147],{"class":77,"line":12138},187,[75,12140,995],{"class":81},[75,12142,12143],{"class":85}," Carries an ",[75,12145,12146],{"class":92},"`exp`",[75,12148,12149],{"class":85}," (expiration) claim.\n",[75,12151,12153,12155,12158,12161],{"class":77,"line":12152},188,[75,12154,995],{"class":81},[75,12156,12157],{"class":85}," (Optionally) carries any other claims you ",[75,12159,12160],{"class":190},"need",[75,12162,12163],{"class":85}," (e.g. user roles, token version, etc.).\n",[75,12165,12167],{"class":77,"line":12166},189,[75,12168,527],{"emptyLinePlaceholder":30},[75,12170,12172,12175,12177,12180,12183],{"class":77,"line":12171},190,[75,12173,12174],{"class":85},"When the client presents that refresh‐",[75,12176,11616],{"class":171},[75,12178,12179],{"class":85},", the server ",[75,12181,12182],{"class":190},"just",[75,12184,11759],{"class":85},[75,12186,12188],{"class":77,"line":12187},191,[75,12189,527],{"emptyLinePlaceholder":30},[75,12191,12193,12195],{"class":77,"line":12192},192,[75,12194,12008],{"class":171},[75,12196,12197],{"class":85}," Verifies its signature against a server‐only signing key.\n",[75,12199,12201,12204,12207,12209,12212,12215],{"class":77,"line":12200},193,[75,12202,12203],{"class":171},"2.",[75,12205,12206],{"class":85}," Checks ",[75,12208,12146],{"class":92},[75,12210,12211],{"class":85}," (and maybe ",[75,12213,12214],{"class":92},"`nbf`",[75,12216,12217],{"class":85},").\n",[75,12219,12221,12224,12227,12229,12232,12234,12237,12239],{"class":77,"line":12220},194,[75,12222,12223],{"class":171},"3.",[75,12225,12226],{"class":85}," If valid, issues a ",[75,12228,11682],{"class":81},[75,12230,12231],{"class":85}," access ",[75,12233,10040],{"class":190},[75,12235,12236],{"class":85}," (and optionally a ",[75,12238,11682],{"class":81},[75,12240,12241],{"class":85}," refresh token).\n",[75,12243,12245],{"class":77,"line":12244},195,[75,12246,527],{"emptyLinePlaceholder":30},[75,12248,12250,12253,12255,12258,12260],{"class":77,"line":12249},196,[75,12251,12252],{"class":85},"No database check—no “lookup” is done. Everything is ",[75,12254,11630],{"class":81},[75,12256,12257],{"class":85}," the ",[75,12259,11616],{"class":171},[75,12261,11927],{"class":85},[75,12263,12265],{"class":77,"line":12264},197,[75,12266,527],{"emptyLinePlaceholder":30},[75,12268,12270,12273,12276,12279,12282,12285,12288],{"class":77,"line":12269},198,[75,12271,12272],{"class":85},"Because there’s no ",[75,12274,12275],{"class":190},"lookup",[75,12277,12278],{"class":85}," (no hash‐table or ",[75,12280,12281],{"class":171},"DB",[75,12283,12284],{"class":85},"), the server ",[75,12286,12287],{"class":190},"cannot",[75,12289,11759],{"class":85},[75,12291,12293],{"class":77,"line":12292},199,[75,12294,527],{"emptyLinePlaceholder":30},[75,12296,12298,12300,12303,12305,12308,12310],{"class":77,"line":12297},200,[75,12299,995],{"class":81},[75,12301,12302],{"class":85}," Immediately revoke a single user’s existing refresh ",[75,12304,10040],{"class":190},[75,12306,12307],{"class":85}," (unless you switch to a ",[75,12309,11682],{"class":81},[75,12311,12312],{"class":85}," signing key or embed some revocation‐list version, which reintroduces state).\n",[75,12314,12316,12318,12321,12323,12326],{"class":77,"line":12315},201,[75,12317,995],{"class":81},[75,12319,12320],{"class":85}," Force‐expire one client’s refresh token early; once it’s signed and unexpired, it’s valid until its ",[75,12322,11616],{"class":171},[75,12324,12325],{"class":92}," `exp`",[75,12327,11927],{"class":85},[75,12329,12331,12333,12336,12338,12341,12343,12346,12348],{"class":77,"line":12330},202,[75,12332,995],{"class":81},[75,12334,12335],{"class":85}," Detect “replay” ",[75,12337,11592],{"class":81},[75,12339,12340],{"class":85}," an old ",[75,12342,10040],{"class":190},[75,12344,12345],{"class":85}," (a stolen refresh ",[75,12347,11616],{"class":171},[75,12349,12350],{"class":85}," can be used until it expires).\n",[75,12352,12354],{"class":77,"line":12353},203,[75,12355,527],{"emptyLinePlaceholder":30},[75,12357,12359,12362],{"class":77,"line":12358},204,[75,12360,12361],{"class":85},"#### Why consider stateless at all",[75,12363,12364],{"class":81},"?\n",[75,12366,12368],{"class":77,"line":12367},205,[75,12369,527],{"emptyLinePlaceholder":30},[75,12371,12373,12375,12378,12380,12383,12386,12389,12392],{"class":77,"line":12372},206,[75,12374,995],{"class":81},[75,12376,12377],{"class":85}," You avoid any server‐side overhead for storing",[75,12379,991],{"class":81},[75,12381,12382],{"class":85},"reading refresh‐token ",[75,12384,12385],{"class":190},"records",[75,12387,12388],{"class":85}," (e.g. no Redis or no ",[75,12390,12391],{"class":171},"SQL",[75,12393,12394],{"class":85}," table).\n",[75,12396,12398,12400,12403,12405],{"class":77,"line":12397},207,[75,12399,995],{"class":81},[75,12401,12402],{"class":85}," Under very high load or simple microservices, you may want to keep your auth servers purely “sign",[75,12404,991],{"class":81},[75,12406,12407],{"class":85},"verify” without hitting a database on every refresh.\n",[75,12409,12411,12413,12416,12418,12421,12423,12426,12428],{"class":77,"line":12410},208,[75,12412,995],{"class":81},[75,12414,12415],{"class":85}," Fewer moving parts can mean easier horizontal scaling",[75,12417,1883],{"class":81},[75,12419,12420],{"class":85}," any instance ",[75,12422,11532],{"class":81},[75,12424,12425],{"class":85}," the same signing key can handle a refresh request ",[75,12427,11630],{"class":81},[75,12429,12430],{"class":85}," one step.\n",[75,12432,12434],{"class":77,"line":12433},209,[75,12435,527],{"emptyLinePlaceholder":30},[75,12437,12439,12441,12443,12446,12448,12451,12454,12456],{"class":77,"line":12438},210,[75,12440,11509],{"class":85},[75,12442,12203],{"class":171},[75,12444,12445],{"class":85}," How to Build a Self",[75,12447,11526],{"class":81},[75,12449,12450],{"class":190},"Contained",[75,12452,12453],{"class":85}," (Stateless) Refresh Token ",[75,12455,11630],{"class":81},[75,12457,12458],{"class":85}," Node.js\n",[75,12460,12462],{"class":77,"line":12461},211,[75,12463,527],{"emptyLinePlaceholder":30},[75,12465,12467,12470,12472,12475,12478,12481,12484,12486,12489,12492,12494,12496],{"class":77,"line":12466},212,[75,12468,12469],{"class":85},"The following is a minimal outline ",[75,12471,11592],{"class":81},[75,12473,12474],{"class":85}," a refresh flow that maintains zero state on the server. It uses ",[75,12476,12477],{"class":190},"JWTs",[75,12479,12480],{"class":85}," (signed with ",[75,12482,12483],{"class":171},"HS256",[75,12485,52],{"class":85},[75,12487,12488],{"class":171},"RS256",[75,12490,12491],{"class":85},"), but any ",[75,12493,12095],{"class":171},[75,12495,11526],{"class":81},[75,12497,12498],{"class":85},"compatible algorithm can be substituted.\n",[75,12500,12502],{"class":77,"line":12501},213,[75,12503,527],{"emptyLinePlaceholder":30},[75,12505,12507,12509,12512],{"class":77,"line":12506},214,[75,12508,12022],{"class":85},[75,12510,12511],{"class":171},"2.1",[75,12513,12514],{"class":85}," Decide on Your Signing Key\n",[75,12516,12518],{"class":77,"line":12517},215,[75,12519,527],{"emptyLinePlaceholder":30},[75,12521,12523,12525,12527,12529,12531,12533],{"class":77,"line":12522},216,[75,12524,11804],{"class":81},[75,12526,12483],{"class":190},[75,12528,6358],{"class":85},[75,12530,11550],{"class":171},[75,12532,476],{"class":85},[75,12534,11816],{"class":81},[75,12536,12538],{"class":77,"line":12537},217,[75,12539,527],{"emptyLinePlaceholder":30},[75,12541,12543],{"class":77,"line":12542},218,[75,12544,12545],{"class":92},"```js\n",[75,12547,12549],{"class":77,"line":12548},219,[75,12550,12551],{"class":92},"// Example: put this in your environment (never check into Git)\n",[75,12553,12555],{"class":77,"line":12554},220,[75,12556,12557],{"class":92},"process.env.JWT_REFRESH_SECRET = 'a‐very‐long‐random‐string‐only‐on‐server';\n",[17,12559,12560],{},"All instances of your auth service use that same secret to sign/verify.",[17,12562,12563],{},[1748,12564,12565],{},"RS256 (RSA):",[66,12567,12569],{"className":68,"code":12568,"language":71,"meta":15,"style":15},"# Generate once (do not share the private key)\nopenssl genrsa -out refresh‐private.pem 2048\nopenssl rsa ‐in refresh‐private.pem ‐pubout ‐out refresh‐public.pem\n",[49,12570,12571,12576,12592],{"__ignoreMap":15},[75,12572,12573],{"class":77,"line":78},[75,12574,12575],{"class":2542},"# Generate once (do not share the private key)\n",[75,12577,12578,12580,12583,12586,12589],{"class":77,"line":25},[75,12579,9620],{"class":190},[75,12581,12582],{"class":92}," genrsa",[75,12584,12585],{"class":171}," -out",[75,12587,12588],{"class":92}," refresh‐private.pem",[75,12590,12591],{"class":171}," 2048\n",[75,12593,12594,12596,12599,12602,12604,12607,12610],{"class":77,"line":215},[75,12595,9620],{"class":190},[75,12597,12598],{"class":92}," rsa",[75,12600,12601],{"class":92}," ‐in",[75,12603,12588],{"class":92},[75,12605,12606],{"class":92}," ‐pubout",[75,12608,12609],{"class":92}," ‐out",[75,12611,12612],{"class":92}," refresh‐public.pem\n",[17,12614,12615,12616,12619,12620,12622],{},"Your server loads ",[49,12617,12618],{},"refresh‐private.pem"," in memory (via ",[49,12621,8840],{},") and uses it to sign every refresh JWT.",[17,12624,12625,12626,1802],{},"On verify, you use ",[49,12627,12628],{},"refresh‐public.pem",[3477,12630,12632],{"id":12631},"_22-define-the-refresh-token-payload","2.2 Define the Refresh-Token Payload",[17,12634,12635],{},"At a minimum, your refresh JWT should contain:",[66,12637,12640],{"className":12638,"code":12639,"language":9122,"meta":15,"style":15},"language-json shiki shiki-themes github-light github-dark github-dark","{\n  \"sub\": \"\u003CuserId>\",            // subject = the user’s unique ID\n  \"typ\": \"refresh\",             // optional: mark it as a refresh token\n  \"iat\": \u003Cissued-at>,             // auto‐included by most libraries \n  \"exp\": \u003Cexpiry timestamp>,      // e.g. now + 30 days\n  // (optionally) \"jti\": \"\u003Csome‐unique‐identifier>\", if you want to blacklist entire “batches” by rotating keys\n}\n",[49,12641,12642,12646,12661,12677,12692,12711,12716],{"__ignoreMap":15},[75,12643,12644],{"class":77,"line":78},[75,12645,8760],{"class":85},[75,12647,12648,12651,12653,12656,12658],{"class":77,"line":25},[75,12649,12650],{"class":171},"  \"sub\"",[75,12652,3179],{"class":85},[75,12654,12655],{"class":92},"\"\u003CuserId>\"",[75,12657,2671],{"class":85},[75,12659,12660],{"class":2542},"// subject = the user’s unique ID\n",[75,12662,12663,12666,12668,12671,12674],{"class":77,"line":215},[75,12664,12665],{"class":171},"  \"typ\"",[75,12667,3179],{"class":85},[75,12669,12670],{"class":92},"\"refresh\"",[75,12672,12673],{"class":85},",             ",[75,12675,12676],{"class":2542},"// optional: mark it as a refresh token\n",[75,12678,12679,12682,12684,12687,12689],{"class":77,"line":221},[75,12680,12681],{"class":171},"  \"iat\"",[75,12683,3179],{"class":85},[75,12685,12686],{"class":8808},"\u003Cissued-at>",[75,12688,12673],{"class":85},[75,12690,12691],{"class":2542},"// auto‐included by most libraries \n",[75,12693,12694,12697,12699,12702,12705,12708],{"class":77,"line":235},[75,12695,12696],{"class":171},"  \"exp\"",[75,12698,3179],{"class":85},[75,12700,12701],{"class":8808},"\u003Cexpiry",[75,12703,12704],{"class":8808}," timestamp>",[75,12706,12707],{"class":85},",      ",[75,12709,12710],{"class":2542},"// e.g. now + 30 days\n",[75,12712,12713],{"class":77,"line":248},[75,12714,12715],{"class":2542},"  // (optionally) \"jti\": \"\u003Csome‐unique‐identifier>\", if you want to blacklist entire “batches” by rotating keys\n",[75,12717,12718],{"class":77,"line":261},[75,12719,325],{"class":85},[1742,12721,12722,12728,12733],{},[1745,12723,12724,12727],{},[49,12725,12726],{},"sub",": your user’s unique identifier.",[1745,12729,12730,12732],{},[49,12731,8650],{},": e.g. now + 30 days if you want a month‐long refresh window.",[1745,12734,12735,12738],{},[49,12736,12737],{},"jti",": JWT ID (optional). If you ever need to revoke “all old tokens,” you can add a rotated key or a “current‐token‐version” in the user’s profile (but that reintroduces some state).",[3477,12740,12742],{"id":12741},"_23-issue-a-refresh-jwt-at-login","2.3 Issue a Refresh JWT at Login",[66,12744,12746],{"className":2039,"code":12745,"language":2041,"meta":15,"style":15},"import jwt from 'jsonwebtoken';\n\n// after user has successfully authenticated:\nfunction issueTokens(userId) {\n  // 1) Access Token (short‐lived)\n  const accessToken = jwt.sign(\n    { sub: userId, typ: 'access' },\n    process.env.JWT_ACCESS_SECRET,   // e.g. HS256 secret or RS256 private key\n    { algorithm: 'HS256', expiresIn: '15m' }\n  );\n\n  // 2) Refresh Token (long‐lived, self‐contained)\n  const refreshToken = jwt.sign(\n    { sub: userId, typ: 'refresh' },\n    process.env.JWT_REFRESH_SECRET,  // only the auth server knows this\n    { algorithm: 'HS256', expiresIn: '30d' }\n  );\n\n  return { accessToken, refreshToken };\n}\n",[49,12747,12748,12760,12764,12769,12783,12788,12802,12812,12825,12837,12841,12845,12850,12864,12873,12886,12899,12903,12907,12914],{"__ignoreMap":15},[75,12749,12750,12752,12754,12756,12758],{"class":77,"line":78},[75,12751,2593],{"class":81},[75,12753,8853],{"class":85},[75,12755,2599],{"class":81},[75,12757,8858],{"class":92},[75,12759,2605],{"class":85},[75,12761,12762],{"class":77,"line":25},[75,12763,527],{"emptyLinePlaceholder":30},[75,12765,12766],{"class":77,"line":215},[75,12767,12768],{"class":2542},"// after user has successfully authenticated:\n",[75,12770,12771,12773,12776,12778,12781],{"class":77,"line":221},[75,12772,10032],{"class":81},[75,12774,12775],{"class":190}," issueTokens",[75,12777,2135],{"class":85},[75,12779,12780],{"class":501},"userId",[75,12782,2273],{"class":85},[75,12784,12785],{"class":77,"line":235},[75,12786,12787],{"class":2542},"  // 1) Access Token (short‐lived)\n",[75,12789,12790,12792,12794,12796,12798,12800],{"class":77,"line":248},[75,12791,2081],{"class":81},[75,12793,8872],{"class":171},[75,12795,605],{"class":81},[75,12797,8877],{"class":85},[75,12799,2501],{"class":190},[75,12801,2197],{"class":85},[75,12803,12804,12807,12810],{"class":77,"line":261},[75,12805,12806],{"class":85},"    { sub: userId, typ: ",[75,12808,12809],{"class":92},"'access'",[75,12811,3776],{"class":85},[75,12813,12814,12817,12820,12822],{"class":77,"line":274},[75,12815,12816],{"class":85},"    process.env.",[75,12818,12819],{"class":171},"JWT_ACCESS_SECRET",[75,12821,4084],{"class":85},[75,12823,12824],{"class":2542},"// e.g. HS256 secret or RS256 private key\n",[75,12826,12827,12829,12831,12833,12835],{"class":77,"line":615},[75,12828,10247],{"class":85},[75,12830,8908],{"class":92},[75,12832,8911],{"class":85},[75,12834,8914],{"class":92},[75,12836,3791],{"class":85},[75,12838,12839],{"class":77,"line":636},[75,12840,2215],{"class":85},[75,12842,12843],{"class":77,"line":642},[75,12844,527],{"emptyLinePlaceholder":30},[75,12846,12847],{"class":77,"line":663},[75,12848,12849],{"class":2542},"  // 2) Refresh Token (long‐lived, self‐contained)\n",[75,12851,12852,12854,12856,12858,12860,12862],{"class":77,"line":669},[75,12853,2081],{"class":81},[75,12855,8953],{"class":171},[75,12857,605],{"class":81},[75,12859,8877],{"class":85},[75,12861,2501],{"class":190},[75,12863,2197],{"class":85},[75,12865,12866,12868,12871],{"class":77,"line":675},[75,12867,12806],{"class":85},[75,12869,12870],{"class":92},"'refresh'",[75,12872,3776],{"class":85},[75,12874,12875,12877,12880,12883],{"class":77,"line":684},[75,12876,12816],{"class":85},[75,12878,12879],{"class":171},"JWT_REFRESH_SECRET",[75,12881,12882],{"class":85},",  ",[75,12884,12885],{"class":2542},"// only the auth server knows this\n",[75,12887,12888,12890,12892,12894,12897],{"class":77,"line":690},[75,12889,10247],{"class":85},[75,12891,8908],{"class":92},[75,12893,8911],{"class":85},[75,12895,12896],{"class":92},"'30d'",[75,12898,3791],{"class":85},[75,12900,12901],{"class":77,"line":2254},[75,12902,2215],{"class":85},[75,12904,12905],{"class":77,"line":2259},[75,12906,527],{"emptyLinePlaceholder":30},[75,12908,12909,12911],{"class":77,"line":2276},[75,12910,6598],{"class":81},[75,12912,12913],{"class":85}," { accessToken, refreshToken };\n",[75,12915,12916],{"class":77,"line":2294},[75,12917,325],{"class":85},[17,12919,12920,12921,12924],{},"Send the ",[49,12922,12923],{},"accessToken"," in JSON response.",[17,12926,12920,12927,12929],{},[49,12928,8980],{}," in an HTTP‐only cookie (or let the client store it in secure storage if you trust your front‐end environment).",[17,12931,12932],{},[1748,12933,12934],{},"Example Express handler:",[66,12936,12938],{"className":2039,"code":12937,"language":2041,"meta":15,"style":15},"app.post('/auth/login', async (req, res) => {\n  // 1. validate credentials (e.g. DB lookup, bcrypt.compare, etc.)\n  // assume user.id == 'alice123'\n  const { accessToken, refreshToken } = issueTokens('alice123');\n\n  res\n    .cookie('refreshToken', refreshToken, {\n      httpOnly: true,\n      secure: process.env.NODE_ENV === 'production',\n      path: '/auth/refresh',\n      maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days\n    })\n    .json({ accessToken });\n});\n",[49,12939,12940,12968,12973,12978,13003,13007,13011,13023,13031,13043,13051,13075,13079,13087],{"__ignoreMap":15},[75,12941,12942,12944,12946,12948,12950,12952,12954,12956,12958,12960,12962,12964,12966],{"class":77,"line":78},[75,12943,2048],{"class":85},[75,12945,10088],{"class":190},[75,12947,2135],{"class":85},[75,12949,10093],{"class":92},[75,12951,1520],{"class":85},[75,12953,10098],{"class":81},[75,12955,6358],{"class":85},[75,12957,2057],{"class":501},[75,12959,1520],{"class":85},[75,12961,2062],{"class":501},[75,12963,2070],{"class":85},[75,12965,2073],{"class":81},[75,12967,2076],{"class":85},[75,12969,12970],{"class":77,"line":25},[75,12971,12972],{"class":2542},"  // 1. validate credentials (e.g. DB lookup, bcrypt.compare, etc.)\n",[75,12974,12975],{"class":77,"line":215},[75,12976,12977],{"class":2542},"  // assume user.id == 'alice123'\n",[75,12979,12980,12982,12984,12986,12988,12990,12992,12994,12996,12998,13001],{"class":77,"line":221},[75,12981,2081],{"class":81},[75,12983,2621],{"class":85},[75,12985,12923],{"class":171},[75,12987,1520],{"class":85},[75,12989,8980],{"class":171},[75,12991,2632],{"class":85},[75,12993,89],{"class":81},[75,12995,12775],{"class":190},[75,12997,2135],{"class":85},[75,12999,13000],{"class":92},"'alice123'",[75,13002,2160],{"class":85},[75,13004,13005],{"class":77,"line":235},[75,13006,527],{"emptyLinePlaceholder":30},[75,13008,13009],{"class":77,"line":248},[75,13010,10375],{"class":85},[75,13012,13013,13015,13017,13019,13021],{"class":77,"line":261},[75,13014,10381],{"class":85},[75,13016,9039],{"class":190},[75,13018,2135],{"class":85},[75,13020,9044],{"class":92},[75,13022,9047],{"class":85},[75,13024,13025,13027,13029],{"class":77,"line":274},[75,13026,10396],{"class":85},[75,13028,1826],{"class":171},[75,13030,2205],{"class":85},[75,13032,13033,13035,13037,13039,13041],{"class":77,"line":615},[75,13034,10406],{"class":85},[75,13036,10409],{"class":171},[75,13038,10412],{"class":81},[75,13040,10415],{"class":92},[75,13042,2205],{"class":85},[75,13044,13045,13047,13049],{"class":77,"line":636},[75,13046,10423],{"class":85},[75,13048,9077],{"class":92},[75,13050,2205],{"class":85},[75,13052,13053,13055,13057,13059,13061,13063,13065,13067,13069,13071,13073],{"class":77,"line":642},[75,13054,10433],{"class":85},[75,13056,9087],{"class":171},[75,13058,9090],{"class":81},[75,13060,9093],{"class":171},[75,13062,9090],{"class":81},[75,13064,9098],{"class":171},[75,13066,9090],{"class":81},[75,13068,9098],{"class":171},[75,13070,9090],{"class":81},[75,13072,9107],{"class":171},[75,13074,9110],{"class":2542},[75,13076,13077],{"class":77,"line":663},[75,13078,10447],{"class":85},[75,13080,13081,13083,13085],{"class":77,"line":669},[75,13082,10381],{"class":85},[75,13084,9122],{"class":190},[75,13086,9125],{"class":85},[75,13088,13089],{"class":77,"line":675},[75,13090,2311],{"class":85},[3477,13092,13094],{"id":13093},"_24-verify-the-stateless-refresh-jwt-no-db-hit","2.4 Verify the Stateless Refresh JWT (No DB Hit)",[17,13096,13097,13098,13100],{},"When the client calls ",[49,13099,9351],{},", you simply:",[3245,13102,13103,13113,13116,13121],{},[1745,13104,13105,13106,13108,13109,13112],{},"Pull ",[49,13107,8980],{}," from the cookie (or from ",[49,13110,13111],{},"Authorization: Bearer",", if you choose).",[1745,13114,13115],{},"Verify its signature and expiry with your single shared secret (or RSA public key).",[1745,13117,13118,13119,2030],{},"If valid → issue a new access token (and optionally a brand‐new refresh JWT with a fresh ",[49,13120,8650],{},[1745,13122,13123],{},"If invalid (expired, malformed, wrong signature) → force a full login.",[66,13125,13127],{"className":2039,"code":13126,"language":2041,"meta":15,"style":15},"app.post('/auth/refresh', (req, res) => {\n  const rawRefresh = req.cookies.refreshToken;\n  if (!rawRefresh) {\n    return res.sendStatus(401);\n  }\n\n  try {\n    // 1) verify signature + expiry\n    const payload = jwt.verify(\n      rawRefresh,\n      process.env.JWT_REFRESH_SECRET, // or the public key if RS256\n      { algorithms: ['HS256'] }\n    );\n    // 2) ensure it’s actually a “refresh” token\n    if (payload.typ !== 'refresh') {\n      throw new Error('Not a refresh token');\n    }\n\n    // 3) payload.sub is the user ID\n    const newAccessToken = jwt.sign(\n      { sub: payload.sub, typ: 'access' },\n      process.env.JWT_ACCESS_SECRET,\n      { algorithm: 'HS256', expiresIn: '15m' }\n    );\n\n    // 4) (OPTIONAL) If you want to rotate your refresh token’s expiry:\n    const newRefreshToken = jwt.sign(\n      { sub: payload.sub, typ: 'refresh' },\n      process.env.JWT_REFRESH_SECRET,\n      { algorithm: 'HS256', expiresIn: '30d' }\n    );\n    // send the new refresh JWT in the cookie\n    res.cookie('refreshToken', newRefreshToken, {\n      httpOnly: true,\n      secure: process.env.NODE_ENV === 'production',\n      path: '/auth/refresh',\n      maxAge: 30 * 24 * 60 * 60 * 1000\n    });\n\n    // 5) send new accessToken in JSON\n    return res.json({ accessToken: newAccessToken });\n  } catch (err) {\n    // invalid signature or expired → force re‐login\n    return res.sendStatus(401);\n  }\n});\n",[49,13128,13129,13153,13164,13175,13189,13193,13197,13203,13208,13222,13227,13239,13249,13254,13259,13274,13290,13295,13299,13304,13318,13327,13335,13348,13352,13356,13361,13376,13384,13392,13404,13408,13413,13426,13434,13446,13454,13476,13481,13485,13490,13500,13508,13513,13527,13531],{"__ignoreMap":15},[75,13130,13131,13133,13135,13137,13139,13141,13143,13145,13147,13149,13151],{"class":77,"line":78},[75,13132,2048],{"class":85},[75,13134,10088],{"class":190},[75,13136,2135],{"class":85},[75,13138,9077],{"class":92},[75,13140,10488],{"class":85},[75,13142,2057],{"class":501},[75,13144,1520],{"class":85},[75,13146,2062],{"class":501},[75,13148,2070],{"class":85},[75,13150,2073],{"class":81},[75,13152,2076],{"class":85},[75,13154,13155,13157,13160,13162],{"class":77,"line":25},[75,13156,2081],{"class":81},[75,13158,13159],{"class":171}," rawRefresh",[75,13161,605],{"class":81},[75,13163,9393],{"class":85},[75,13165,13166,13168,13170,13172],{"class":77,"line":215},[75,13167,532],{"class":81},[75,13169,6358],{"class":85},[75,13171,6361],{"class":81},[75,13173,13174],{"class":85},"rawRefresh) {\n",[75,13176,13177,13179,13181,13183,13185,13187],{"class":77,"line":221},[75,13178,749],{"class":81},[75,13180,2281],{"class":85},[75,13182,2284],{"class":190},[75,13184,2135],{"class":85},[75,13186,9240],{"class":171},[75,13188,2160],{"class":85},[75,13190,13191],{"class":77,"line":235},[75,13192,2165],{"class":85},[75,13194,13195],{"class":77,"line":248},[75,13196,527],{"emptyLinePlaceholder":30},[75,13198,13199,13201],{"class":77,"line":261},[75,13200,10593],{"class":81},[75,13202,2076],{"class":85},[75,13204,13205],{"class":77,"line":274},[75,13206,13207],{"class":2542},"    // 1) verify signature + expiry\n",[75,13209,13210,13212,13214,13216,13218,13220],{"class":77,"line":615},[75,13211,10601],{"class":81},[75,13213,3847],{"class":171},[75,13215,605],{"class":81},[75,13217,8877],{"class":85},[75,13219,2536],{"class":190},[75,13221,2197],{"class":85},[75,13223,13224],{"class":77,"line":636},[75,13225,13226],{"class":85},"      rawRefresh,\n",[75,13228,13229,13232,13234,13236],{"class":77,"line":642},[75,13230,13231],{"class":85},"      process.env.",[75,13233,12879],{"class":171},[75,13235,1520],{"class":85},[75,13237,13238],{"class":2542},"// or the public key if RS256\n",[75,13240,13241,13244,13246],{"class":77,"line":663},[75,13242,13243],{"class":85},"      { algorithms: [",[75,13245,8908],{"class":92},[75,13247,13248],{"class":85},"] }\n",[75,13250,13251],{"class":77,"line":669},[75,13252,13253],{"class":85},"    );\n",[75,13255,13256],{"class":77,"line":675},[75,13257,13258],{"class":2542},"    // 2) ensure it’s actually a “refresh” token\n",[75,13260,13261,13263,13266,13269,13272],{"class":77,"line":684},[75,13262,594],{"class":81},[75,13264,13265],{"class":85}," (payload.typ ",[75,13267,13268],{"class":81},"!==",[75,13270,13271],{"class":92}," 'refresh'",[75,13273,2273],{"class":85},[75,13275,13276,13279,13281,13283,13285,13288],{"class":77,"line":690},[75,13277,13278],{"class":81},"      throw",[75,13280,6372],{"class":81},[75,13282,6375],{"class":190},[75,13284,2135],{"class":85},[75,13286,13287],{"class":92},"'Not a refresh token'",[75,13289,2160],{"class":85},[75,13291,13292],{"class":77,"line":2254},[75,13293,13294],{"class":85},"    }\n",[75,13296,13297],{"class":77,"line":2259},[75,13298,527],{"emptyLinePlaceholder":30},[75,13300,13301],{"class":77,"line":2276},[75,13302,13303],{"class":2542},"    // 3) payload.sub is the user ID\n",[75,13305,13306,13308,13310,13312,13314,13316],{"class":77,"line":2294},[75,13307,10601],{"class":81},[75,13309,9485],{"class":171},[75,13311,605],{"class":81},[75,13313,8877],{"class":85},[75,13315,2501],{"class":190},[75,13317,2197],{"class":85},[75,13319,13320,13323,13325],{"class":77,"line":2299},[75,13321,13322],{"class":85},"      { sub: payload.sub, typ: ",[75,13324,12809],{"class":92},[75,13326,3776],{"class":85},[75,13328,13329,13331,13333],{"class":77,"line":2308},[75,13330,13231],{"class":85},[75,13332,12819],{"class":171},[75,13334,2205],{"class":85},[75,13336,13337,13340,13342,13344,13346],{"class":77,"line":2792},[75,13338,13339],{"class":85},"      { algorithm: ",[75,13341,8908],{"class":92},[75,13343,8911],{"class":85},[75,13345,8914],{"class":92},[75,13347,3791],{"class":85},[75,13349,13350],{"class":77,"line":2802},[75,13351,13253],{"class":85},[75,13353,13354],{"class":77,"line":2810},[75,13355,527],{"emptyLinePlaceholder":30},[75,13357,13358],{"class":77,"line":2815},[75,13359,13360],{"class":2542},"    // 4) (OPTIONAL) If you want to rotate your refresh token’s expiry:\n",[75,13362,13363,13365,13368,13370,13372,13374],{"class":77,"line":3092},[75,13364,10601],{"class":81},[75,13366,13367],{"class":171}," newRefreshToken",[75,13369,605],{"class":81},[75,13371,8877],{"class":85},[75,13373,2501],{"class":190},[75,13375,2197],{"class":85},[75,13377,13378,13380,13382],{"class":77,"line":3103},[75,13379,13322],{"class":85},[75,13381,12870],{"class":92},[75,13383,3776],{"class":85},[75,13385,13386,13388,13390],{"class":77,"line":3114},[75,13387,13231],{"class":85},[75,13389,12879],{"class":171},[75,13391,2205],{"class":85},[75,13393,13394,13396,13398,13400,13402],{"class":77,"line":3120},[75,13395,13339],{"class":85},[75,13397,8908],{"class":92},[75,13399,8911],{"class":85},[75,13401,12896],{"class":92},[75,13403,3791],{"class":85},[75,13405,13406],{"class":77,"line":3126},[75,13407,13253],{"class":85},[75,13409,13410],{"class":77,"line":3147},[75,13411,13412],{"class":2542},"    // send the new refresh JWT in the cookie\n",[75,13414,13415,13417,13419,13421,13423],{"class":77,"line":5604},[75,13416,2129],{"class":85},[75,13418,9039],{"class":190},[75,13420,2135],{"class":85},[75,13422,9044],{"class":92},[75,13424,13425],{"class":85},", newRefreshToken, {\n",[75,13427,13428,13430,13432],{"class":77,"line":5610},[75,13429,10396],{"class":85},[75,13431,1826],{"class":171},[75,13433,2205],{"class":85},[75,13435,13436,13438,13440,13442,13444],{"class":77,"line":5630},[75,13437,10406],{"class":85},[75,13439,10409],{"class":171},[75,13441,10412],{"class":81},[75,13443,10415],{"class":92},[75,13445,2205],{"class":85},[75,13447,13448,13450,13452],{"class":77,"line":5645},[75,13449,10423],{"class":85},[75,13451,9077],{"class":92},[75,13453,2205],{"class":85},[75,13455,13456,13458,13460,13462,13464,13466,13468,13470,13472,13474],{"class":77,"line":5650},[75,13457,10433],{"class":85},[75,13459,9087],{"class":171},[75,13461,9090],{"class":81},[75,13463,9093],{"class":171},[75,13465,9090],{"class":81},[75,13467,9098],{"class":171},[75,13469,9090],{"class":81},[75,13471,9098],{"class":171},[75,13473,9090],{"class":81},[75,13475,10441],{"class":171},[75,13477,13478],{"class":77,"line":5655},[75,13479,13480],{"class":85},"    });\n",[75,13482,13483],{"class":77,"line":5661},[75,13484,527],{"emptyLinePlaceholder":30},[75,13486,13487],{"class":77,"line":5682},[75,13488,13489],{"class":2542},"    // 5) send new accessToken in JSON\n",[75,13491,13492,13494,13496,13498],{"class":77,"line":5687},[75,13493,749],{"class":81},[75,13495,2281],{"class":85},[75,13497,9122],{"class":190},[75,13499,9567],{"class":85},[75,13501,13502,13504,13506],{"class":77,"line":5704},[75,13503,10656],{"class":85},[75,13505,9221],{"class":81},[75,13507,9224],{"class":85},[75,13509,13510],{"class":77,"line":5710},[75,13511,13512],{"class":2542},"    // invalid signature or expired → force re‐login\n",[75,13514,13515,13517,13519,13521,13523,13525],{"class":77,"line":5719},[75,13516,749],{"class":81},[75,13518,2281],{"class":85},[75,13520,2284],{"class":190},[75,13522,2135],{"class":85},[75,13524,9240],{"class":171},[75,13526,2160],{"class":85},[75,13528,13529],{"class":77,"line":5726},[75,13530,2165],{"class":85},[75,13532,13533],{"class":77,"line":5732},[75,13534,2311],{"class":85},[1742,13536,13537,13540,13549],{},[1745,13538,13539],{},"No database or in‐memory lookup is ever performed.",[1745,13541,13542,13543,13545,13546,13548],{},"The ",[49,13544,8599],{}," call alone both checks the signature and confirms ",[49,13547,8650],{}," hasn’t passed.",[1745,13550,13551,13552,13554],{},"You could optionally rotate the refresh JWT on each use (as shown), but that still remains stateless—every old refresh token (signed earlier) is still valid until its original ",[49,13553,8650],{}," passes.",[58,13556,13558],{"id":13557},"_3-trade-offs-security-implications","3. Trade-Offs & Security Implications",[3477,13560,13562],{"id":13561},"_31-you-lose-per-token-revocation","3.1 You Lose Per-Token Revocation",[17,13564,13565],{},"If you issue a refresh‐JWT that’s valid for 30 days, and the user’s device is stolen on day 5, you cannot immediately revoke it.",[17,13567,13568,13569,13571],{},"The only way to “revoke” would be to rotate your entire ",[49,13570,12879],{}," (or RSA private key), which instantly invalidates all outstanding refresh tokens for all users. That’s almost never acceptable in a large system.",[17,13573,13574],{},"In other words, stateless refresh tokens force you to accept that once signed, each token lives until its built-in expiry (unless you rotate the global signing key).",[3477,13576,13578],{"id":13577},"_32-you-cannot-track-or-invalidate-individual-sessions","3.2 You Cannot Track or Invalidate Individual Sessions",[17,13580,13581],{},"You have no way (server‐side) to see which device/session is using which refresh token.",[17,13583,13584],{},"You cannot “log out” a single device without “log out everyone” (key rotation) or embedding some “revokedTokensList” reference that reintroduces state.",[3477,13586,13588],{"id":13587},"_33-replay-risk-is-permanent-until-expiration","3.3 Replay Risk Is Permanent Until Expiration",[17,13590,13591,13592,13594,13595,13597],{},"If someone exfiltrates a valid refresh JWT, they can keep calling ",[49,13593,9351],{}," until that JWT’s ",[49,13596,8650],{}," timestamp passes.",[17,13599,13600],{},"You have no way to detect that replay happens because you never store “this token was already used.”",[17,13602,13603,13604,13606],{},"In a more stateful design, you might store a ",[49,13605,12737],{}," (JWT ID) in a DB and flag it as used or revoked; stateless means you don’t.",[3477,13608,13610],{"id":13609},"_34-clock-skew-expiry-boundaries","3.4 Clock-Skew & Expiry Boundaries",[17,13612,13613],{},"You must ensure all your servers’ clocks are tightly synchronized (e.g. via NTP).",[17,13615,13616],{},"If a client’s clock is off by a few minutes, it may try to refresh a token that your server already considers expired (or vice versa).",[58,13618,13620],{"id":13619},"_4-when-is-stateless-refresh-reasonable","4. When Is Stateless Refresh Reasonable?",[1742,13622,13623,13631,13639,13647],{},[1745,13624,13625,13628,13630],{},[1748,13626,13627],{},"Small/Short-Lived Deployments or Internal Tools",[6038,13629],{},"If you don’t care about revocation or compromised refresh tokens—e.g. internal tools that rotate keys every day.",[1745,13632,13633,13636,13638],{},[1748,13634,13635],{},"Low-Risk Data",[6038,13637],{},"If the access tokens only grant access to non-critical resources (e.g. a public RSS feed).",[1745,13640,13641,13644,13646],{},[1748,13642,13643],{},"Very Low Friction Environments",[6038,13645],{},"If you can tolerate “force-expire everyone if the key leaks.”",[1745,13648,13649,13652,13654],{},[1748,13650,13651],{},"Microservices That Rely on Downstream Validation Only",[6038,13653],{},"If your gateways or downstream services need to verify refresh tokens without hitting a central DB, you save that hop—but accept the lack of revocation.",[58,13656,13658],{"id":13657},"_5-alternatives-that-keep-some-state-but-minimize-it","5. Alternatives That Keep Some “State” But Minimize It",[17,13660,13661],{},"If you want most of the scaling benefits of statelessness but still occasionally need to revoke or blacklist:",[3477,13663,13665],{"id":13664},"_51-store-a-token-version-or-token-counter-in-the-users-record","5.1 Store a “Token Version” or “Token Counter” in the User’s Record",[1742,13667,13668,13674,13680],{},[1745,13669,13670,13671,1802],{},"In each refresh‐JWT’s payload, embed a custom claim, e.g. ",[49,13672,13673],{},"ver: 3",[1745,13675,13676,13677,1802],{},"In your user table, store ",[49,13678,13679],{},"currentRefreshVersion = 3",[1745,13681,13682,13683,13685,13686,13746],{},"On ",[49,13684,9351],{},", do:",[66,13687,13689],{"className":2039,"code":13688,"language":2041,"meta":15,"style":15},"const payload = jwt.verify(...);\nif (payload.ver !== user.currentRefreshVersion) throw 401;\n// otherwise, issue new tokens with ver++\nupdate user.currentRefreshVersion in DB;\n",[49,13690,13691,13710,13729,13734],{"__ignoreMap":15},[75,13692,13693,13695,13697,13699,13701,13703,13705,13708],{"class":77,"line":78},[75,13694,2490],{"class":81},[75,13696,3847],{"class":171},[75,13698,605],{"class":81},[75,13700,8877],{"class":85},[75,13702,2536],{"class":190},[75,13704,2135],{"class":85},[75,13706,13707],{"class":81},"...",[75,13709,2160],{"class":85},[75,13711,13712,13714,13717,13719,13722,13724,13727],{"class":77,"line":25},[75,13713,154],{"class":81},[75,13715,13716],{"class":85}," (payload.ver ",[75,13718,13268],{"class":81},[75,13720,13721],{"class":85}," user.currentRefreshVersion) ",[75,13723,5264],{"class":81},[75,13725,13726],{"class":171}," 401",[75,13728,2605],{"class":85},[75,13730,13731],{"class":77,"line":215},[75,13732,13733],{"class":2542},"// otherwise, issue new tokens with ver++\n",[75,13735,13736,13739,13741,13744],{"class":77,"line":221},[75,13737,13738],{"class":85},"update user.currentRefreshVersion ",[75,13740,11630],{"class":81},[75,13742,13743],{"class":171}," DB",[75,13745,2605],{"class":85},[1742,13747,13748,13757],{},[1745,13749,13750,13753,13754,1802],{},[1748,13751,13752],{},"Pros:"," You only store a single integer per user. You can revoke all refresh tokens for user “alice” by bumping ",[49,13755,13756],{},"currentRefreshVersion",[1745,13758,13759,13762,13763,13766,13767,13770],{},[1748,13760,13761],{},"Cons:"," You still need exactly one DB lookup per refresh (to check ",[49,13764,13765],{},"payload.ver"," against ",[49,13768,13769],{},"user.currentRefreshVersion","). It’s not completely stateless, but it’s minimal state.",[3477,13772,13774],{"id":13773},"_52-shorten-the-refresh-token-expiry","5.2 Shorten the Refresh Token Expiry",[17,13776,13777],{},"If you’re worried about stolen refresh tokens, reduce the TTL from 30 days to say 7 days or even 24 hours.",[1742,13779,13780,13783],{},[1745,13781,13782],{},"The shorter its window, the less time an attacker has to abuse stolen credentials.",[1745,13784,13785],{},"But now your clients must refresh more frequently.",[3477,13787,13789],{"id":13788},"_53-use-a-sliding-window-in-a-very-thin-cache","5.3 Use a “Sliding Window” in a Very Thin Cache",[1742,13791,13792,13798,13801],{},[1745,13793,13794,13795,13797],{},"Keep a small in‐memory LRU cache (or Redis) of recently issued ",[49,13796,12737],{}," values.",[1745,13799,13800],{},"Whenever a refresh‐JWT is used, you check “have I seen this jti already? If yes, possibly replay attack. If no, mark it seen for next 10 minutes.”",[1745,13802,13803],{},"This only catches immediate replay, not longer‐term theft. Also, it’s still a stateful store, just extremely short‐lived.",[58,13805,13807],{"id":13806},"_6-code-example-stateless-vs-minimal-state","6. Code Example: Stateless vs. Minimal State",[17,13809,13810],{},"The following is a side-by-side sketch:",[3477,13812,13814],{"id":13813},"_61-fully-stateless-no-db-at-all","6.1 Fully Stateless (No DB at All)",[66,13816,13818],{"className":2039,"code":13817,"language":2041,"meta":15,"style":15},"// REFRESH_SECRET is an HS256 secret only on server\nconst REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;\n\n// Issue at login:\nfunction issueStatelessRefreshToken(userId) {\n  // no “jti” or DB check—just a long‐lived exp\n  return jwt.sign(\n    { sub: userId, typ: 'refresh' },\n    REFRESH_SECRET,\n    { algorithm: 'HS256', expiresIn: '30d' }\n  );\n}\n\n// /auth/refresh route:\napp.post('/auth/refresh', (req, res) => {\n  const token = req.cookies.refreshToken;\n  if (!token) return res.sendStatus(401);\n\n  try {\n    const payload = jwt.verify(token, REFRESH_SECRET, { algorithms: ['HS256'] });\n    if (payload.typ !== 'refresh') throw new Error();\n    // OK: issue new access & (optionally) new refresh\n    const accessToken = jwt.sign({ sub: payload.sub, typ: 'access' }, ACCESS_SECRET, { expiresIn: '15m' });\n    // If rotating, generate a brand‐new refresh here:\n    const newRefresh = jwt.sign({ sub: payload.sub, typ: 'refresh' }, REFRESH_SECRET, { expiresIn: '30d' });\n    res.cookie('refreshToken', newRefresh, { httpOnly: true, secure: true, path: '/auth/refresh', maxAge: 30*24*60*60*1000 });\n    return res.json({ accessToken });\n  } catch (e) {\n    return res.sendStatus(401);\n  }\n});\n",[49,13819,13820,13825,13840,13844,13849,13862,13867,13877,13885,13892,13904,13908,13912,13916,13921,13945,13955,13977,13981,13987,14011,14031,14036,14066,14071,14098,14149,14159,14168,14182,14186],{"__ignoreMap":15},[75,13821,13822],{"class":77,"line":78},[75,13823,13824],{"class":2542},"// REFRESH_SECRET is an HS256 secret only on server\n",[75,13826,13827,13829,13832,13834,13836,13838],{"class":77,"line":25},[75,13828,2490],{"class":81},[75,13830,13831],{"class":171}," REFRESH_SECRET",[75,13833,605],{"class":81},[75,13835,6344],{"class":85},[75,13837,12879],{"class":171},[75,13839,2605],{"class":85},[75,13841,13842],{"class":77,"line":215},[75,13843,527],{"emptyLinePlaceholder":30},[75,13845,13846],{"class":77,"line":221},[75,13847,13848],{"class":2542},"// Issue at login:\n",[75,13850,13851,13853,13856,13858,13860],{"class":77,"line":235},[75,13852,10032],{"class":81},[75,13854,13855],{"class":190}," issueStatelessRefreshToken",[75,13857,2135],{"class":85},[75,13859,12780],{"class":501},[75,13861,2273],{"class":85},[75,13863,13864],{"class":77,"line":248},[75,13865,13866],{"class":2542},"  // no “jti” or DB check—just a long‐lived exp\n",[75,13868,13869,13871,13873,13875],{"class":77,"line":261},[75,13870,6598],{"class":81},[75,13872,8877],{"class":85},[75,13874,2501],{"class":190},[75,13876,2197],{"class":85},[75,13878,13879,13881,13883],{"class":77,"line":274},[75,13880,12806],{"class":85},[75,13882,12870],{"class":92},[75,13884,3776],{"class":85},[75,13886,13887,13890],{"class":77,"line":615},[75,13888,13889],{"class":171},"    REFRESH_SECRET",[75,13891,2205],{"class":85},[75,13893,13894,13896,13898,13900,13902],{"class":77,"line":636},[75,13895,10247],{"class":85},[75,13897,8908],{"class":92},[75,13899,8911],{"class":85},[75,13901,12896],{"class":92},[75,13903,3791],{"class":85},[75,13905,13906],{"class":77,"line":642},[75,13907,2215],{"class":85},[75,13909,13910],{"class":77,"line":663},[75,13911,325],{"class":85},[75,13913,13914],{"class":77,"line":669},[75,13915,527],{"emptyLinePlaceholder":30},[75,13917,13918],{"class":77,"line":675},[75,13919,13920],{"class":2542},"// /auth/refresh route:\n",[75,13922,13923,13925,13927,13929,13931,13933,13935,13937,13939,13941,13943],{"class":77,"line":684},[75,13924,2048],{"class":85},[75,13926,10088],{"class":190},[75,13928,2135],{"class":85},[75,13930,9077],{"class":92},[75,13932,10488],{"class":85},[75,13934,2057],{"class":501},[75,13936,1520],{"class":85},[75,13938,2062],{"class":501},[75,13940,2070],{"class":85},[75,13942,2073],{"class":81},[75,13944,2076],{"class":85},[75,13946,13947,13949,13951,13953],{"class":77,"line":690},[75,13948,2081],{"class":81},[75,13950,10534],{"class":171},[75,13952,605],{"class":81},[75,13954,9393],{"class":85},[75,13956,13957,13959,13961,13963,13965,13967,13969,13971,13973,13975],{"class":77,"line":2254},[75,13958,532],{"class":81},[75,13960,6358],{"class":85},[75,13962,6361],{"class":81},[75,13964,10570],{"class":85},[75,13966,9560],{"class":81},[75,13968,2281],{"class":85},[75,13970,2284],{"class":190},[75,13972,2135],{"class":85},[75,13974,9240],{"class":171},[75,13976,2160],{"class":85},[75,13978,13979],{"class":77,"line":2259},[75,13980,527],{"emptyLinePlaceholder":30},[75,13982,13983,13985],{"class":77,"line":2276},[75,13984,10593],{"class":81},[75,13986,2076],{"class":85},[75,13988,13989,13991,13993,13995,13997,13999,14001,14004,14007,14009],{"class":77,"line":2294},[75,13990,10601],{"class":81},[75,13992,3847],{"class":171},[75,13994,605],{"class":81},[75,13996,8877],{"class":85},[75,13998,2536],{"class":190},[75,14000,10612],{"class":85},[75,14002,14003],{"class":171},"REFRESH_SECRET",[75,14005,14006],{"class":85},", { algorithms: [",[75,14008,8908],{"class":92},[75,14010,9307],{"class":85},[75,14012,14013,14015,14017,14019,14021,14023,14025,14027,14029],{"class":77,"line":2299},[75,14014,594],{"class":81},[75,14016,13265],{"class":85},[75,14018,13268],{"class":81},[75,14020,13271],{"class":92},[75,14022,2070],{"class":85},[75,14024,5264],{"class":81},[75,14026,6372],{"class":81},[75,14028,6375],{"class":190},[75,14030,2305],{"class":85},[75,14032,14033],{"class":77,"line":2308},[75,14034,14035],{"class":2542},"    // OK: issue new access & (optionally) new refresh\n",[75,14037,14038,14040,14042,14044,14046,14048,14051,14053,14056,14059,14062,14064],{"class":77,"line":2792},[75,14039,10601],{"class":81},[75,14041,8872],{"class":171},[75,14043,605],{"class":81},[75,14045,8877],{"class":85},[75,14047,2501],{"class":190},[75,14049,14050],{"class":85},"({ sub: payload.sub, typ: ",[75,14052,12809],{"class":92},[75,14054,14055],{"class":85}," }, ",[75,14057,14058],{"class":171},"ACCESS_SECRET",[75,14060,14061],{"class":85},", { expiresIn: ",[75,14063,8914],{"class":92},[75,14065,3144],{"class":85},[75,14067,14068],{"class":77,"line":2802},[75,14069,14070],{"class":2542},"    // If rotating, generate a brand‐new refresh here:\n",[75,14072,14073,14075,14078,14080,14082,14084,14086,14088,14090,14092,14094,14096],{"class":77,"line":2810},[75,14074,10601],{"class":81},[75,14076,14077],{"class":171}," newRefresh",[75,14079,605],{"class":81},[75,14081,8877],{"class":85},[75,14083,2501],{"class":190},[75,14085,14050],{"class":85},[75,14087,12870],{"class":92},[75,14089,14055],{"class":85},[75,14091,14003],{"class":171},[75,14093,14061],{"class":85},[75,14095,12896],{"class":92},[75,14097,3144],{"class":85},[75,14099,14100,14102,14104,14106,14108,14111,14113,14116,14118,14121,14123,14126,14128,14130,14133,14135,14138,14140,14142,14144,14147],{"class":77,"line":2815},[75,14101,2129],{"class":85},[75,14103,9039],{"class":190},[75,14105,2135],{"class":85},[75,14107,9044],{"class":92},[75,14109,14110],{"class":85},", newRefresh, { httpOnly: ",[75,14112,1826],{"class":171},[75,14114,14115],{"class":85},", secure: ",[75,14117,1826],{"class":171},[75,14119,14120],{"class":85},", path: ",[75,14122,9077],{"class":92},[75,14124,14125],{"class":85},", maxAge: ",[75,14127,9087],{"class":171},[75,14129,995],{"class":81},[75,14131,14132],{"class":171},"24",[75,14134,995],{"class":81},[75,14136,14137],{"class":171},"60",[75,14139,995],{"class":81},[75,14141,14137],{"class":171},[75,14143,995],{"class":81},[75,14145,14146],{"class":171},"1000",[75,14148,3144],{"class":85},[75,14150,14151,14153,14155,14157],{"class":77,"line":3092},[75,14152,749],{"class":81},[75,14154,2281],{"class":85},[75,14156,9122],{"class":190},[75,14158,9125],{"class":85},[75,14160,14161,14163,14165],{"class":77,"line":3103},[75,14162,10656],{"class":85},[75,14164,9221],{"class":81},[75,14166,14167],{"class":85}," (e) {\n",[75,14169,14170,14172,14174,14176,14178,14180],{"class":77,"line":3114},[75,14171,749],{"class":81},[75,14173,2281],{"class":85},[75,14175,2284],{"class":190},[75,14177,2135],{"class":85},[75,14179,9240],{"class":171},[75,14181,2160],{"class":85},[75,14183,14184],{"class":77,"line":3120},[75,14185,2165],{"class":85},[75,14187,14188],{"class":77,"line":3126},[75,14189,2311],{"class":85},[1742,14191,14192,14195],{},[1745,14193,14194],{},"No DB calls anywhere.",[1745,14196,14197,14198,14200,14201,14203],{},"Once a refresh JWT is issued, it lives until its own ",[49,14199,8650],{}," passes (or until you rotate ",[49,14202,14003],{}," globally).",[3477,14205,14207],{"id":14206},"_62-minimal-state-single-token-version-field","6.2 Minimal State: Single “Token Version” Field",[66,14209,14211],{"className":2039,"code":14210,"language":2041,"meta":15,"style":15},"// Assume a user table schema: { id, email, passwordHash, refreshVersion }\nimport User from './models/User.js'; // (ORM or raw query, whatever)\n\n// At login:\nasync function loginHandler(req, res) {\n  const { email, password } = req.body;\n  const user = await User.findOne({ where: { email } });\n  if (!user || !compareHash(password, user.passwordHash)) {\n    return res.sendStatus(401);\n  }\n\n  // Issue access\n  const accessToken = jwt.sign(\n    { sub: user.id, typ: 'access' },\n    ACCESS_SECRET,\n    { algorithm: 'HS256', expiresIn: '15m' }\n  );\n\n  // Issue refresh with “ver” equal to user.refreshVersion\n  const refreshToken = jwt.sign(\n    { sub: user.id, typ: 'refresh', ver: user.refreshVersion },\n    REFRESH_SECRET,\n    { algorithm: 'HS256', expiresIn: '30d' }\n  );\n\n  res\n    .cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, path: '/auth/refresh', maxAge: 30*24*60*60*1000 })\n    .json({ accessToken });\n}\n\n// /auth/refresh route:\napp.post('/auth/refresh', async (req, res) => {\n  const token = req.cookies.refreshToken;\n  if (!token) return res.sendStatus(401);\n\n  let payload;\n  try {\n    payload = jwt.verify(token, REFRESH_SECRET, { algorithms: ['HS256'] });\n  } catch {\n    return res.sendStatus(401);\n  }\n  if (payload.typ !== 'refresh') return res.sendStatus(401);\n\n  // 1) Look up user row to check version\n  const user = await User.findByPk(payload.sub);\n  if (!user) return res.sendStatus(401);\n\n  // 2) If token’s ver ≠ user.refreshVersion → it’s been revoked/stale\n  if (payload.ver !== user.refreshVersion) {\n    return res.sendStatus(401);\n  }\n\n  // 3) Everything lines up → issue new tokens, bump version\n  const newAccess = jwt.sign(\n    { sub: user.id, typ: 'access' },\n    ACCESS_SECRET,\n    { algorithm: 'HS256', expiresIn: '15m' }\n  );\n  // bump refreshVersion\n  await user.update({ refreshVersion: user.refreshVersion + 1 });\n\n  // issue a brand‐new refresh with the new version\n  const newRefresh = jwt.sign(\n    { sub: user.id, typ: 'refresh', ver: user.refreshVersion },\n    REFRESH_SECRET,\n    { algorithm: 'HS256', expiresIn: '30d' }\n  );\n\n  res\n    .cookie('refreshToken', newRefresh, { httpOnly: true, secure: true, path: '/auth/refresh', maxAge: 30*24*60*60*1000 })\n    .json({ accessToken: newAccess });\n});\n",[49,14212,14213,14218,14235,14239,14244,14263,14281,14300,14320,14334,14338,14342,14347,14361,14370,14377,14389,14393,14397,14402,14416,14425,14431,14443,14447,14451,14455,14500,14508,14512,14516,14520,14548,14558,14580,14584,14591,14597,14618,14626,14640,14644,14668,14672,14677,14695,14718,14722,14727,14738,14752,14756,14760,14765,14780,14788,14794,14806,14810,14815,14835,14839,14844,14858,14866,14872,14884,14888,14892,14896,14940,14949],{"__ignoreMap":15},[75,14214,14215],{"class":77,"line":78},[75,14216,14217],{"class":2542},"// Assume a user table schema: { id, email, passwordHash, refreshVersion }\n",[75,14219,14220,14222,14225,14227,14230,14232],{"class":77,"line":25},[75,14221,2593],{"class":81},[75,14223,14224],{"class":85}," User ",[75,14226,2599],{"class":81},[75,14228,14229],{"class":92}," './models/User.js'",[75,14231,9942],{"class":85},[75,14233,14234],{"class":2542},"// (ORM or raw query, whatever)\n",[75,14236,14237],{"class":77,"line":215},[75,14238,527],{"emptyLinePlaceholder":30},[75,14240,14241],{"class":77,"line":221},[75,14242,14243],{"class":2542},"// At login:\n",[75,14245,14246,14248,14250,14253,14255,14257,14259,14261],{"class":77,"line":235},[75,14247,10098],{"class":81},[75,14249,6496],{"class":81},[75,14251,14252],{"class":190}," loginHandler",[75,14254,2135],{"class":85},[75,14256,2057],{"class":501},[75,14258,1520],{"class":85},[75,14260,2062],{"class":501},[75,14262,2273],{"class":85},[75,14264,14265,14267,14269,14271,14273,14275,14277,14279],{"class":77,"line":248},[75,14266,2081],{"class":81},[75,14268,2621],{"class":85},[75,14270,10121],{"class":171},[75,14272,1520],{"class":85},[75,14274,10126],{"class":171},[75,14276,2632],{"class":85},[75,14278,89],{"class":81},[75,14280,10133],{"class":85},[75,14282,14283,14285,14287,14289,14291,14294,14297],{"class":77,"line":261},[75,14284,2081],{"class":81},[75,14286,10145],{"class":171},[75,14288,605],{"class":81},[75,14290,9436],{"class":81},[75,14292,14293],{"class":85}," User.",[75,14295,14296],{"class":190},"findOne",[75,14298,14299],{"class":85},"({ where: { email } });\n",[75,14301,14302,14304,14306,14308,14310,14312,14314,14317],{"class":77,"line":274},[75,14303,532],{"class":81},[75,14305,6358],{"class":85},[75,14307,6361],{"class":81},[75,14309,10166],{"class":85},[75,14311,10169],{"class":81},[75,14313,10172],{"class":81},[75,14315,14316],{"class":190},"compareHash",[75,14318,14319],{"class":85},"(password, user.passwordHash)) {\n",[75,14321,14322,14324,14326,14328,14330,14332],{"class":77,"line":615},[75,14323,749],{"class":81},[75,14325,2281],{"class":85},[75,14327,2284],{"class":190},[75,14329,2135],{"class":85},[75,14331,9240],{"class":171},[75,14333,2160],{"class":85},[75,14335,14336],{"class":77,"line":636},[75,14337,2165],{"class":85},[75,14339,14340],{"class":77,"line":642},[75,14341,527],{"emptyLinePlaceholder":30},[75,14343,14344],{"class":77,"line":663},[75,14345,14346],{"class":2542},"  // Issue access\n",[75,14348,14349,14351,14353,14355,14357,14359],{"class":77,"line":669},[75,14350,2081],{"class":81},[75,14352,8872],{"class":171},[75,14354,605],{"class":81},[75,14356,8877],{"class":85},[75,14358,2501],{"class":190},[75,14360,2197],{"class":85},[75,14362,14363,14366,14368],{"class":77,"line":675},[75,14364,14365],{"class":85},"    { sub: user.id, typ: ",[75,14367,12809],{"class":92},[75,14369,3776],{"class":85},[75,14371,14372,14375],{"class":77,"line":684},[75,14373,14374],{"class":171},"    ACCESS_SECRET",[75,14376,2205],{"class":85},[75,14378,14379,14381,14383,14385,14387],{"class":77,"line":690},[75,14380,10247],{"class":85},[75,14382,8908],{"class":92},[75,14384,8911],{"class":85},[75,14386,8914],{"class":92},[75,14388,3791],{"class":85},[75,14390,14391],{"class":77,"line":2254},[75,14392,2215],{"class":85},[75,14394,14395],{"class":77,"line":2259},[75,14396,527],{"emptyLinePlaceholder":30},[75,14398,14399],{"class":77,"line":2276},[75,14400,14401],{"class":2542},"  // Issue refresh with “ver” equal to user.refreshVersion\n",[75,14403,14404,14406,14408,14410,14412,14414],{"class":77,"line":2294},[75,14405,2081],{"class":81},[75,14407,8953],{"class":171},[75,14409,605],{"class":81},[75,14411,8877],{"class":85},[75,14413,2501],{"class":190},[75,14415,2197],{"class":85},[75,14417,14418,14420,14422],{"class":77,"line":2299},[75,14419,14365],{"class":85},[75,14421,12870],{"class":92},[75,14423,14424],{"class":85},", ver: user.refreshVersion },\n",[75,14426,14427,14429],{"class":77,"line":2308},[75,14428,13889],{"class":171},[75,14430,2205],{"class":85},[75,14432,14433,14435,14437,14439,14441],{"class":77,"line":2792},[75,14434,10247],{"class":85},[75,14436,8908],{"class":92},[75,14438,8911],{"class":85},[75,14440,12896],{"class":92},[75,14442,3791],{"class":85},[75,14444,14445],{"class":77,"line":2802},[75,14446,2215],{"class":85},[75,14448,14449],{"class":77,"line":2810},[75,14450,527],{"emptyLinePlaceholder":30},[75,14452,14453],{"class":77,"line":2815},[75,14454,10375],{"class":85},[75,14456,14457,14459,14461,14463,14465,14468,14470,14472,14474,14476,14478,14480,14482,14484,14486,14488,14490,14492,14494,14496,14498],{"class":77,"line":3092},[75,14458,10381],{"class":85},[75,14460,9039],{"class":190},[75,14462,2135],{"class":85},[75,14464,9044],{"class":92},[75,14466,14467],{"class":85},", refreshToken, { httpOnly: ",[75,14469,1826],{"class":171},[75,14471,14115],{"class":85},[75,14473,1826],{"class":171},[75,14475,14120],{"class":85},[75,14477,9077],{"class":92},[75,14479,14125],{"class":85},[75,14481,9087],{"class":171},[75,14483,995],{"class":81},[75,14485,14132],{"class":171},[75,14487,995],{"class":81},[75,14489,14137],{"class":171},[75,14491,995],{"class":81},[75,14493,14137],{"class":171},[75,14495,995],{"class":81},[75,14497,14146],{"class":171},[75,14499,11280],{"class":85},[75,14501,14502,14504,14506],{"class":77,"line":3103},[75,14503,10381],{"class":85},[75,14505,9122],{"class":190},[75,14507,9125],{"class":85},[75,14509,14510],{"class":77,"line":3114},[75,14511,325],{"class":85},[75,14513,14514],{"class":77,"line":3120},[75,14515,527],{"emptyLinePlaceholder":30},[75,14517,14518],{"class":77,"line":3126},[75,14519,13920],{"class":2542},[75,14521,14522,14524,14526,14528,14530,14532,14534,14536,14538,14540,14542,14544,14546],{"class":77,"line":3147},[75,14523,2048],{"class":85},[75,14525,10088],{"class":190},[75,14527,2135],{"class":85},[75,14529,9077],{"class":92},[75,14531,1520],{"class":85},[75,14533,10098],{"class":81},[75,14535,6358],{"class":85},[75,14537,2057],{"class":501},[75,14539,1520],{"class":85},[75,14541,2062],{"class":501},[75,14543,2070],{"class":85},[75,14545,2073],{"class":81},[75,14547,2076],{"class":85},[75,14549,14550,14552,14554,14556],{"class":77,"line":5604},[75,14551,2081],{"class":81},[75,14553,10534],{"class":171},[75,14555,605],{"class":81},[75,14557,9393],{"class":85},[75,14559,14560,14562,14564,14566,14568,14570,14572,14574,14576,14578],{"class":77,"line":5610},[75,14561,532],{"class":81},[75,14563,6358],{"class":85},[75,14565,6361],{"class":81},[75,14567,10570],{"class":85},[75,14569,9560],{"class":81},[75,14571,2281],{"class":85},[75,14573,2284],{"class":190},[75,14575,2135],{"class":85},[75,14577,9240],{"class":171},[75,14579,2160],{"class":85},[75,14581,14582],{"class":77,"line":5630},[75,14583,527],{"emptyLinePlaceholder":30},[75,14585,14586,14588],{"class":77,"line":5645},[75,14587,6538],{"class":81},[75,14589,14590],{"class":85}," payload;\n",[75,14592,14593,14595],{"class":77,"line":5650},[75,14594,10593],{"class":81},[75,14596,2076],{"class":85},[75,14598,14599,14602,14604,14606,14608,14610,14612,14614,14616],{"class":77,"line":5655},[75,14600,14601],{"class":85},"    payload ",[75,14603,89],{"class":81},[75,14605,8877],{"class":85},[75,14607,2536],{"class":190},[75,14609,10612],{"class":85},[75,14611,14003],{"class":171},[75,14613,14006],{"class":85},[75,14615,8908],{"class":92},[75,14617,9307],{"class":85},[75,14619,14620,14622,14624],{"class":77,"line":5661},[75,14621,10656],{"class":85},[75,14623,9221],{"class":81},[75,14625,2076],{"class":85},[75,14627,14628,14630,14632,14634,14636,14638],{"class":77,"line":5682},[75,14629,749],{"class":81},[75,14631,2281],{"class":85},[75,14633,2284],{"class":190},[75,14635,2135],{"class":85},[75,14637,9240],{"class":171},[75,14639,2160],{"class":85},[75,14641,14642],{"class":77,"line":5687},[75,14643,2165],{"class":85},[75,14645,14646,14648,14650,14652,14654,14656,14658,14660,14662,14664,14666],{"class":77,"line":5704},[75,14647,532],{"class":81},[75,14649,13265],{"class":85},[75,14651,13268],{"class":81},[75,14653,13271],{"class":92},[75,14655,2070],{"class":85},[75,14657,9560],{"class":81},[75,14659,2281],{"class":85},[75,14661,2284],{"class":190},[75,14663,2135],{"class":85},[75,14665,9240],{"class":171},[75,14667,2160],{"class":85},[75,14669,14670],{"class":77,"line":5710},[75,14671,527],{"emptyLinePlaceholder":30},[75,14673,14674],{"class":77,"line":5719},[75,14675,14676],{"class":2542},"  // 1) Look up user row to check version\n",[75,14678,14679,14681,14683,14685,14687,14689,14692],{"class":77,"line":5726},[75,14680,2081],{"class":81},[75,14682,10145],{"class":171},[75,14684,605],{"class":81},[75,14686,9436],{"class":81},[75,14688,14293],{"class":85},[75,14690,14691],{"class":190},"findByPk",[75,14693,14694],{"class":85},"(payload.sub);\n",[75,14696,14697,14699,14701,14703,14706,14708,14710,14712,14714,14716],{"class":77,"line":5732},[75,14698,532],{"class":81},[75,14700,6358],{"class":85},[75,14702,6361],{"class":81},[75,14704,14705],{"class":85},"user) ",[75,14707,9560],{"class":81},[75,14709,2281],{"class":85},[75,14711,2284],{"class":190},[75,14713,2135],{"class":85},[75,14715,9240],{"class":171},[75,14717,2160],{"class":85},[75,14719,14720],{"class":77,"line":5737},[75,14721,527],{"emptyLinePlaceholder":30},[75,14723,14724],{"class":77,"line":10372},[75,14725,14726],{"class":2542},"  // 2) If token’s ver ≠ user.refreshVersion → it’s been revoked/stale\n",[75,14728,14729,14731,14733,14735],{"class":77,"line":10378},[75,14730,532],{"class":81},[75,14732,13716],{"class":85},[75,14734,13268],{"class":81},[75,14736,14737],{"class":85}," user.refreshVersion) {\n",[75,14739,14740,14742,14744,14746,14748,14750],{"class":77,"line":10393},[75,14741,749],{"class":81},[75,14743,2281],{"class":85},[75,14745,2284],{"class":190},[75,14747,2135],{"class":85},[75,14749,9240],{"class":171},[75,14751,2160],{"class":85},[75,14753,14754],{"class":77,"line":10403},[75,14755,2165],{"class":85},[75,14757,14758],{"class":77,"line":10420},[75,14759,527],{"emptyLinePlaceholder":30},[75,14761,14762],{"class":77,"line":10430},[75,14763,14764],{"class":2542},"  // 3) Everything lines up → issue new tokens, bump version\n",[75,14766,14767,14769,14772,14774,14776,14778],{"class":77,"line":10444},[75,14768,2081],{"class":81},[75,14770,14771],{"class":171}," newAccess",[75,14773,605],{"class":81},[75,14775,8877],{"class":85},[75,14777,2501],{"class":190},[75,14779,2197],{"class":85},[75,14781,14782,14784,14786],{"class":77,"line":10450},[75,14783,14365],{"class":85},[75,14785,12809],{"class":92},[75,14787,3776],{"class":85},[75,14789,14790,14792],{"class":77,"line":10459},[75,14791,14374],{"class":171},[75,14793,2205],{"class":85},[75,14795,14796,14798,14800,14802,14804],{"class":77,"line":10464},[75,14797,10247],{"class":85},[75,14799,8908],{"class":92},[75,14801,8911],{"class":85},[75,14803,8914],{"class":92},[75,14805,3791],{"class":85},[75,14807,14808],{"class":77,"line":10469},[75,14809,2215],{"class":85},[75,14811,14812],{"class":77,"line":10475},[75,14813,14814],{"class":2542},"  // bump refreshVersion\n",[75,14816,14817,14820,14823,14825,14828,14830,14833],{"class":77,"line":10503},[75,14818,14819],{"class":81},"  await",[75,14821,14822],{"class":85}," user.",[75,14824,3358],{"class":190},[75,14826,14827],{"class":85},"({ refreshVersion: user.refreshVersion ",[75,14829,6612],{"class":81},[75,14831,14832],{"class":171}," 1",[75,14834,3144],{"class":85},[75,14836,14837],{"class":77,"line":10529},[75,14838,527],{"emptyLinePlaceholder":30},[75,14840,14841],{"class":77,"line":10561},[75,14842,14843],{"class":2542},"  // issue a brand‐new refresh with the new version\n",[75,14845,14846,14848,14850,14852,14854,14856],{"class":77,"line":10585},[75,14847,2081],{"class":81},[75,14849,14077],{"class":171},[75,14851,605],{"class":81},[75,14853,8877],{"class":85},[75,14855,2501],{"class":190},[75,14857,2197],{"class":85},[75,14859,14860,14862,14864],{"class":77,"line":10590},[75,14861,14365],{"class":85},[75,14863,12870],{"class":92},[75,14865,14424],{"class":85},[75,14867,14868,14870],{"class":77,"line":10598},[75,14869,13889],{"class":171},[75,14871,2205],{"class":85},[75,14873,14874,14876,14878,14880,14882],{"class":77,"line":10619},[75,14875,10247],{"class":85},[75,14877,8908],{"class":92},[75,14879,8911],{"class":85},[75,14881,12896],{"class":92},[75,14883,3791],{"class":85},[75,14885,14886],{"class":77,"line":10625},[75,14887,2215],{"class":85},[75,14889,14890],{"class":77,"line":10635},[75,14891,527],{"emptyLinePlaceholder":30},[75,14893,14894],{"class":77,"line":10653},[75,14895,10375],{"class":85},[75,14897,14898,14900,14902,14904,14906,14908,14910,14912,14914,14916,14918,14920,14922,14924,14926,14928,14930,14932,14934,14936,14938],{"class":77,"line":10663},[75,14899,10381],{"class":85},[75,14901,9039],{"class":190},[75,14903,2135],{"class":85},[75,14905,9044],{"class":92},[75,14907,14110],{"class":85},[75,14909,1826],{"class":171},[75,14911,14115],{"class":85},[75,14913,1826],{"class":171},[75,14915,14120],{"class":85},[75,14917,9077],{"class":92},[75,14919,14125],{"class":85},[75,14921,9087],{"class":171},[75,14923,995],{"class":81},[75,14925,14132],{"class":171},[75,14927,995],{"class":81},[75,14929,14137],{"class":171},[75,14931,995],{"class":81},[75,14933,14137],{"class":171},[75,14935,995],{"class":81},[75,14937,14146],{"class":171},[75,14939,11280],{"class":85},[75,14941,14942,14944,14946],{"class":77,"line":10678},[75,14943,10381],{"class":85},[75,14945,9122],{"class":190},[75,14947,14948],{"class":85},"({ accessToken: newAccess });\n",[75,14950,14951],{"class":77,"line":10683},[75,14952,2311],{"class":85},[1742,14954,14955,14961,14967],{},[1745,14956,14957,14958,1802],{},"One extra read and one extra write (per refresh) to check/increment ",[49,14959,14960],{},"refreshVersion",[1745,14962,14963,14964,14966],{},"You get immediate revocation (by incrementing ",[49,14965,14960],{}," if you suspect compromise).",[1745,14968,14969],{},"You still do not store every single token string—just a single integer per user. This is often a good middle ground.",[58,14971,14973],{"id":14972},"_7-when-and-why-to-avoid-fully-stateless-refresh","7. When (and Why) to Avoid Fully Stateless Refresh",[3477,14975,14977],{"id":14976},"regulatory-or-security-requirements","Regulatory or Security Requirements",[1742,14979,14980,14983],{},[1745,14981,14982],{},"If regulators require the ability to revoke tokens on demand (e.g. when a user requests “log me out of all devices”), you need some state.",[1745,14984,14985],{},"If your app handles highly sensitive data (banking, healthcare), you cannot afford to let a stolen refresh JWT live until expiry.",[3477,14987,14989],{"id":14988},"user-experience","User Experience",[1742,14991,14992,14995],{},[1745,14993,14994],{},"If you want to allow users to “log out — immediately,” you must change something server‐side. A stateless JWT can’t detect a logout until it expires.",[1745,14996,14997],{},"If you want to show “Active sessions in other browsers/devices” and let the user revoke them, you need to record each issuance server‐side.",[3477,14999,15001],{"id":15000},"granular-token-control","Granular Token Control",[1742,15003,15004],{},[1745,15005,15006],{},"If you want to revoke a single device’s refresh token (but leave others active), you must track each one separately in a DB or store a per‐token entry somewhere.",[58,15008,15010],{"id":15009},"_8-summary-of-recommendations","8. Summary of Recommendations",[1742,15012,15013,15032,15057],{},[1745,15014,15015,15018],{},[1748,15016,15017],{},"If you truly want zero state (fully stateless):",[1742,15019,15020,15023,15026,15029],{},[1745,15021,15022],{},"Issue refresh tokens as self-contained JWTs signed with a server‐only key.",[1745,15024,15025],{},"Verify them purely by signature+exp.",[1745,15027,15028],{},"Accept that you cannot revoke them individually—only rotate your global signing key to kill them all.",[1745,15030,15031],{},"Be prepared for replay risk until each token’s exp runs out.",[1745,15033,15034,15037],{},[1748,15035,15036],{},"If you need minimal state but still low overhead:",[1742,15038,15039,15042,15054],{},[1745,15040,15041],{},"Store a single integer (“token version”) or a single timestamp (“issuedAt cut‐off”) per user.",[1745,15043,15044,15045,52,15048,15051,15052,1802],{},"Embed that version/timestamp in each refresh JWT (",[49,15046,15047],{},"ver",[49,15049,15050],{},"iat",") and check it on ",[49,15053,9351],{},[1745,15055,15056],{},"This lets you revoke or “expire all previously issued refresh tokens” for that user in one DB write, but still avoids storing every token string.",[1745,15058,15059,15062],{},[1748,15060,15061],{},"If you need full revocation or per-device tracking:",[1742,15063,15064,15070,15073],{},[1745,15065,15066,15067,15069],{},"Store each refresh token’s hash (or ",[49,15068,12737],{},") in Redis or SQL with an expiry.",[1745,15071,15072],{},"On each refresh, check/rotate/delete that single token record.",[1745,15074,15075],{},"This is the most secure, but it requires a stateful lookup on every refresh (and some storage cleanup process).",[17,15077,15078],{},[1748,15079,6854],{},[17,15081,15082,15083,15085],{},"Stateless (in-memory-free) means your refresh token is a signed JWT with an ",[49,15084,8650],{},". The server only checks signature+expiry, never a database. That does achieve full “statelessness,” but you forfeit on-demand revocation or forced logout for individual sessions.",[17,15087,15088,15089,15092],{},"If you can live with “refresh tokens are valid until their built-in expiry, unless you rotate the global signing key,” then a self-contained JWT is fine. Otherwise, introduce some minimal state (e.g. ",[49,15090,15091],{},"token‐version"," per user) to regain revocation capability without a full in‐memory table of tokens.",[17,15094,15095],{},"Choose the approach whose security guarantees and operational overhead best match your application’s risk profile.",[1737,15097,15099],{"id":15098},"part-3-layered-defense-against-refresh-token-theft","Part 3: Layered Defense Against Refresh Token Theft",[17,15101,15102],{},"Even with strong standard security measures in place (HttpOnly + Secure + SameSite cookies, one‐time refresh tokens, CSRF tokens on the refresh endpoint, short TTLs, and anomaly detection), there is still a fundamental problem anytime an attacker extracts the raw token from a browser:",[3960,15104,15105],{},[17,15106,15107],{},"Once an attacker has the exact string that was issued to a user, nothing in server‐side “require both token and userId” logic will stop them from replaying it.",[17,15109,15110],{},"This section covers where attackers typically succeed, how standard mitigations help, and what additional defenses can be layered on top to make real‐world compromise significantly harder.",[58,15112,15114],{"id":15113},"_1-why-someone-literally-steals-the-cookie-is-the-worstcase-scenario","1. Why “someone literally steals the cookie” is the worst‐case scenario",[1742,15116,15117,15120,15130],{},[1745,15118,15119],{},"If an attacker exfiltrates the refresh‐token cookie, they already know exactly which user it belongs to (their browser holds that token, and it was set under that user’s session). Asking for an extra user‐ID or CSRF value at refresh time won’t block a malicious script that already controls the victim’s browser context.",[1745,15121,15122,15125,15126,15129],{},[1748,15123,15124],{},"XSS"," is the most common way cookies get stolen—even with HttpOnly set, there are clever workarounds (e.g. if an attacker can modify the page to submit a hidden form to your refresh endpoint, they can force the user’s browser to send the HttpOnly cookie for them). ",[49,15127,15128],{},"SameSite=Strict"," helps mitigate that, but only if the attacker’s payload can’t run from the same top‐level origin.",[1745,15131,15132,15135,15136,15139,15140,15142],{},[1748,15133,15134],{},"Phishing"," or “push a malicious browser extension” can simply read the cookie jar or do a ",[49,15137,15138],{},"fetch('/auth/refresh')"," on behalf of the user (sending the HttpOnly cookie automatically). Again, ",[49,15141,15128],{}," only stops cross‐site GET/POST if the browser honors it, but it won’t help if malicious code is already running inside your origin.",[3960,15144,15145],{},[17,15146,15147,15150],{},[1748,15148,15149],{},"In short:"," once a refresh token is exfiltrated at the client, it is effectively “in the wild” and can be replayed—no server‐side check of userId or visitor_id will stop that.",[58,15152,15154],{"id":15153},"_2-standard-baseline-mitigations","2. Standard Baseline Mitigations",[1742,15156,15157,15163,15169,15175,15184],{},[1745,15158,15159,15162],{},[1748,15160,15161],{},"HttpOnly + Secure + SameSite=Strict cookies","\nThis is the single most important control for protecting tokens from “simple” XSS or cross‐site request forgery.",[1745,15164,15165,15168],{},[1748,15166,15167],{},"Very short TTL (3–5 days)","\nLimits the window during which a stolen token works.",[1745,15170,15171,15174],{},[1748,15172,15173],{},"One‐time‐use / revocation flag","\nBy marking each token as invalid once it’s used (and storing that in the DB), you prevent “long‐lived, unrevokable tokens.” If your server sees the same refresh token used twice, you know something’s up.",[1745,15176,15177,15180,15181,15183],{},[1748,15178,15179],{},"CSRF token on the refresh endpoint","\nIf an attacker tries to trick the user’s browser into POSTing to ",[49,15182,9351],{},", they will need to know your dynamically generated CSRF token. (Assuming you’ve stored that token in a secure, same‐site cookie or in a hidden form input.)",[1745,15185,15186,15189],{},[1748,15187,15188],{},"Additional re‐auth checks for very sensitive routes","\nEmail confirmation or requiring the old password again for “change password” or “change email” actions means that even if someone hijacks a session, they still can’t immediately alter highly‐sensitive settings without that second factor.",[3960,15191,15192,15198,15212],{},[17,15193,15194,15197],{},[1748,15195,15196],{},"All of these measures collectively shrink the “attack surface.”"," A would‐be attacker now needs to:",[3245,15199,15200,15203,15206,15209],{},[1745,15201,15202],{},"Run code inside your origin (to bypass SameSite/HttpOnly),",[1745,15204,15205],{},"Extract the raw refresh cookie,",[1745,15207,15208],{},"Observe the CSRF token or trick the page into including it,",[1745,15210,15211],{},"Present the token before it expires or is revoked.",[17,15213,15214],{},"That is orders of magnitude harder than simply “guessing a userId,” so you’re on the right track.",[58,15216,15218],{"id":15217},"_3-where-realworld-attackers-still-succeedand-how-to-make-that-harder","3. Where real‐world attackers still succeed—and how to make that harder",[17,15220,15221],{},"The following additional layers can be implemented. While none are silver bullets, each significantly raises the bar:",[3477,15223,15225],{"id":15224},"a-refreshtoken-rotation-untrusted-device-flag","A. Refresh‐token rotation + “untrusted device” flag",[1742,15227,15228,15231,15251],{},[1745,15229,15230],{},"Rotate on every use (you’re already invalidating each token once it’s used). That means if an attacker steals a valid token, the moment the real user’s app does a “silent” refresh, it will invalidate the stolen token.",[1745,15232,15233,15234],{},"In parallel, record fingerprint or “device ID” whenever you issue a token. That could be a hash of:",[1742,15235,15236,15239,15245,15248],{},[1745,15237,15238],{},"User‐agent string",[1745,15240,15241,15242,1497],{},"A random GUID your frontend generated (and stored in ",[49,15243,15244],{},"localStorage",[1745,15246,15247],{},"IP address (with care—mobile users often change IPs)",[1745,15249,15250],{},"Any other non‐spoofable client attribute (e.g. a TLS client certificate or WebAuthn key).",[1745,15252,15253,15254,15257,15258],{},"When you generate the original refresh token, save ",[49,15255,15256],{},"device_id = ABC123"," alongside it. Then, on subsequent refresh calls, re‐compute that same “device ID” from the request. If it doesn’t match the value stored in the DB, treat this request as suspicious:",[3245,15259,15260,15263],{},[1745,15261,15262],{},"Force a full re‐login (throw away the stolen token), or",[1745,15264,15265],{},"Send an email “We detected a login from a new device/IP. Was this you?”",[3960,15267,15268],{},[17,15269,15270,15271,15273],{},"This way, even if someone copies the raw token, they cannot present the correct “device ID.” They could try to imitate it, but if you choose a fingerprint that’s hard to spoof (e.g. a random GUID stored in ",[49,15272,15244],{},"), you will catch the thief.",[3477,15275,15277],{"id":15276},"b-anomaly-detection-riskbased-throttling","B. Anomaly detection & risk‐based throttling",[1742,15279,15280,15283,15286],{},[1745,15281,15282],{},"Log every refresh attempt (timestamp, IP address, userAgent, device fingerprint).",[1745,15284,15285],{},"If your system sees “User 42’s refresh token was used by IP=1.2.3.4 at 3 pm, and then by IP=5.6.7.8 five minutes later,” you can immediately revoke all outstanding refresh tokens for that user.",[1745,15287,15288],{},"You can also temporarily lock down the account or force an MFA challenge if there’s unusual behavior (e.g. thousands of refresh attempts in a short time, or a geo-location jump from USA → Europe within minutes).",[3477,15290,15292],{"id":15291},"c-offline-revocation-list-forced-re-authentication-after-inactivity","C. Offline revocation list + forced re-authentication after inactivity",[1742,15294,15295],{},[1745,15296,15297,15298],{},"Even though you mark each refresh token invalid on use, you might also:",[1742,15299,15300,15311],{},[1745,15301,15302,15303,15306,15307,15310],{},"Keep a global ",[49,15304,15305],{},"userIsLockedOut"," flag in your users table. If you detect any suspicious refresh behavior, set ",[49,15308,15309],{},"users.isLocked = true",". Then on every refresh or API call, you immediately reject until the user explicitly re-logs in (and perhaps changes their password).",[1745,15312,15313],{},"Have a short inactivity timer: if the user hasn’t used the app in, say, 24 hours, force a full login—even if their refresh token hasn’t technically expired. This reduces the window for replay.",[3477,15315,15317],{"id":15316},"d-device-browser-isolation-with-subdomains","D. Device / browser isolation with subdomains",[1742,15319,15320,15331,15338],{},[1745,15321,15322,15323,15326,15327,15330],{},"If you serve your front end on ",[49,15324,15325],{},"app.example.com"," but your refresh endpoint runs on ",[49,15328,15329],{},"auth.example.com",", you can limit cookie scope even further.",[1745,15332,15333,15334,15337],{},"Issue the refresh cookie with ",[49,15335,15336],{},"Domain=auth.example.com; Path=/; Secure; HttpOnly; SameSite=None"," (or whatever is appropriate).",[1745,15339,15340,15341,15343,15344,15347,15348,2845,15351,1802],{},"Your JavaScript on ",[49,15342,15325],{}," never sees that cookie at all. To invoke a refresh, you do a hidden ",[49,15345,15346],{},"\u003Ciframe src=\"https://auth.example.com/refresh\">"," or a ",[49,15349,15350],{},"fetch",[49,15352,15353],{},"credentials: 'include'",[3960,15355,15356],{},[17,15357,15358,15359,15361,15362,15364],{},"Even if an XSS vulnerability exists on ",[49,15360,15325],{},", it cannot read or exfiltrate cookies set under ",[49,15363,15329],{},". It can only force the browser to send them, which at least denies the attacker the ability to steal the raw token.",[3477,15366,15368],{"id":15367},"e-enforce-mfa-step-up-authentication-for-sensitive-actions","E. Enforce MFA / step-up authentication for sensitive actions",[1742,15370,15371],{},[1745,15372,15373,15374],{},"You mentioned that password changes require the old password and email verification. You can extend this principle:",[1742,15375,15376,15379],{},[1745,15377,15378],{},"Every N days, force an MFA re-prompt. E.g. once a week, for any action—even a simple refresh—you require a one-time code.",[1745,15380,15381],{},"For any refresh from a new device or new IP, step up to 2FA.",[3960,15383,15384],{},[17,15385,15386],{},"That way, even if someone has the raw token, they cannot refresh it from an unfamiliar context without that second factor.",[3477,15388,15390],{"id":15389},"f-content-security-policy-csp-xss-hardening","F. Content Security Policy (CSP) / XSS hardening",[1742,15392,15393,15409,15412],{},[1745,15394,15395,15396,15399,15400,3497,15403,7226,15406,1802],{},"Lock down your CSP so that even if an attacker can inject a small ",[49,15397,15398],{},"\u003Cscript>",", they cannot load external JS or steal cookies easily. Use ",[49,15401,15402],{},"script-src 'self'",[49,15404,15405],{},"'nonce-…'",[49,15407,15408],{},"style-src 'self'",[1745,15410,15411],{},"Audit any third-party libs/plugins that might open an XSS vector. A single malicious NPM dependency can bypass HttpOnly by inserting inline scripts that forward cookies as form posts.",[1745,15413,15414],{},"Use strict input sanitization / encoding to prevent stored XSS in user profiles or comments. The fewer XSS vectors you have, the harder it is for an attacker to exfiltrate cookies.",[58,15416,15418],{"id":15417},"_4-if-you-absolutely-must-bind-tokens-to-a-specific-user-id","4. If you absolutely must bind tokens to a specific user ID",[17,15420,15421,15422,15425],{},"Requiring the user to send both ",[49,15423,15424],{},"userId + token"," can make automated attacks slightly more cumbersome in theory, but in practice:",[1742,15427,15428,15435,15438],{},[1745,15429,15430,15431,15434],{},"User ID is often public—it can appear in your JSON API responses, in profile pages, in client-side code, etc. An attacker who already controls a malicious script in the user’s browser can simply read the DOM or make a quick ",[49,15432,15433],{},"GET /api/me"," call to learn the numeric ID.",[1745,15436,15437],{},"If an attacker steals token Y from user 42, they already have Y and already know this was issued for user 42. Trying Y with any other ID will fail. Trying Y with “42” will succeed. There’s no need to protect that numerical “42” value; it’s not a secret.",[1745,15439,15440,15441,15444],{},"A “",[49,15442,15443],{},"JOIN on token_hash","” query alone is sufficient to prove “token Y belongs to user 42.” You don’t gain anything meaningful by also requiring “AND user_id = 42” in SQL—because the only time SQL will return a row is when both are true. It only increases your code complexity slightly, without significantly enhancing security.",[58,15446,15448],{"id":15447},"_5-summary-a-layered-defense-in-depth-approach","5. Summary: a layered, defense-in-depth approach",[3477,15450,15452],{"id":15451},"prevent-theft-in-the-first-place","Prevent theft in the first place",[1742,15454,15455,15461,15464],{},[1745,15456,15457,15460],{},[49,15458,15459],{},"HttpOnly + Secure + SameSite=Strict"," on your refresh cookie.",[1745,15462,15463],{},"Strong CSP, input sanitization, minimal third-party JS.",[1745,15465,15466],{},"Frequent security audits of any frontend code that handles tokens.",[3477,15468,15470],{"id":15469},"detect-respond-quickly-if-theft-happens","Detect & respond quickly if theft happens",[1742,15472,15473,15476,15479],{},[1745,15474,15475],{},"Rotate refresh tokens on every use (one-time use) and immediately revoke the old one.",[1745,15477,15478],{},"Log “device fingerprint” or IP whenever a refresh occurs; revoke all tokens on anomalies.",[1745,15480,15481],{},"Maintain a “user locked” flag if you see suspicious refresh patterns.",[3477,15483,15485],{"id":15484},"add-step-up-checks-for-sensitive-contexts","Add step-up checks for sensitive contexts",[1742,15487,15488,15491],{},[1745,15489,15490],{},"CSRF token on the refresh endpoint (you already have).",[1745,15492,15493],{},"Require a fresh MFA or password‐reentry for any “account settings” call.",[3477,15495,15497],{"id":15496},"choose-a-single-clear-lookup-mechanism","Choose a single, clear lookup mechanism",[1742,15499,15500],{},[1745,15501,15502,15503,15506,15507,15509],{},"Either “lookup by token only” (with a unique index), or “lookup by ",[49,15504,15505],{},"token_hash","” alone. In both cases, the attacker who has the raw token can always discover the ",[49,15508,12780],{},". What really matters is that the database will return exactly one row for that token, so you know “this belongs to user N.”",[3960,15511,15512],{},[17,15513,15514,15515,15517],{},"Personally, I’d do “lookup by token only” with a UNIQUE constraint on ",[49,15516,15505],{},", because it’s simpler and requires fewer round trips.",[17,15519,15520],{},[1748,15521,15522],{},"Bottom‐line",[17,15524,15525],{},"Even if you bind “token + userId” together in your SQL, any attacker who already has the raw token string can trivially learn (or guess) the correct userId. The only way to truly break that chain is to prevent token theft in the first place or to implement strong step-up checks (MFA, email confirmations, device fingerprinting) that invalidate a stolen token the moment it’s used from an unfamiliar environment.",[1697,15527,15528],{},"html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sByVh, html code.shiki .sByVh{--shiki-light:#22863A;--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .sVAnh, html code.shiki .sVAnh{--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-default:#FDAEB7;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":15,"searchDepth":25,"depth":25,"links":15530},[15531,15546],{"id":8559,"depth":25,"text":8560,"children":15532},[15533,15534,15535,15536,15537,15538,15539,15540,15541,15542,15543,15544,15545],{"id":8571,"depth":215,"text":8572},{"id":8621,"depth":215,"text":8622},{"id":8689,"depth":215,"text":8690},{"id":8716,"depth":215,"text":8717},{"id":9587,"depth":215,"text":9588},{"id":9608,"depth":215,"text":9609},{"id":9786,"depth":215,"text":9787},{"id":13557,"depth":215,"text":13558},{"id":13619,"depth":215,"text":13620},{"id":13657,"depth":215,"text":13658},{"id":13806,"depth":215,"text":13807},{"id":14972,"depth":215,"text":14973},{"id":15009,"depth":215,"text":15010},{"id":15098,"depth":25,"text":15099,"children":15547},[15548,15549,15550,15551,15552],{"id":15113,"depth":215,"text":15114},{"id":15153,"depth":215,"text":15154},{"id":15217,"depth":215,"text":15218},{"id":15417,"depth":215,"text":15418},{"id":15447,"depth":215,"text":15448},"An in-depth guide on implementing JSON Web Tokens (JWT) using refresh tokens and private keys.","/knowledge-base/auth.jpg",{},"/knowledge-base/jwt","20",{"title":8554,"description":15553},{"loc":15556},"knowledge-base/jwt",[11616,15562,2386,6979],"Authentication","QglOTZjnVTDEqWWgogimMWP2zN9R2NX1U6d4yeqqeBU",{"id":15565,"title":15566,"body":15567,"category":2373,"contentType":16766,"date":1712,"description":16767,"extension":28,"head":27,"image":16768,"meta":16769,"navigation":30,"ogImage":27,"path":16770,"readingTime":16771,"robots":27,"schemaOrg":27,"seo":16772,"sitemap":16773,"stem":16774,"tags":16775,"__hash__":16778},"knowledgeBase/knowledge-base/mtls.md","mTLS CookBook With Caddy",{"type":7,"value":15568,"toc":16748},[15569,15572,15576,15579,15676,15680,15683,15796,15808,15812,15815,15819,15826,15990,15994,15997,16044,16052,16056,16059,16063,16066,16155,16159,16162,16323,16327,16334,16422,16434,16438,16441,16445,16448,16544,16548,16551,16617,16621,16624,16683,16686,16745],[17,15570,15571],{},"Mutual TLS (mTLS) provides robust authentication by requiring both the server and client to present valid certificates. This tutorial guides you through setting up mTLS with Caddy, creating a private certificate authority (CA), and configuring client certificates.",[1737,15573,15575],{"id":15574},"overview","Overview",[17,15577,15578],{},"This setup uses four distinct nodes to demonstrate a complete infrastructure. The examples use internal IP addresses (LAN and WireGuard) to restrict access.",[1464,15580,15581,15596],{},[1467,15582,15583],{},[1470,15584,15585,15588,15590,15593],{},[1473,15586,15587],{},"Node",[1473,15589,6106],{},[1473,15591,15592],{},"LAN IP",[1473,15594,15595],{},"WireGuard IP",[1481,15597,15598,15616,15636,15656],{},[1470,15599,15600,15605,15608,15613],{},[1486,15601,15602],{},[1748,15603,15604],{},"Database VM",[1486,15606,15607],{},"MySQL, Grafana, and Loki",[1486,15609,15610],{},[49,15611,15612],{},"10.10.10.1",[1486,15614,15615],{},"None",[1470,15617,15618,15623,15626,15631],{},[1486,15619,15620],{},[1748,15621,15622],{},"API VM",[1486,15624,15625],{},"Backend server (e.g., Strapi) running Caddy with mTLS",[1486,15627,15628],{},[49,15629,15630],{},"10.10.10.2",[1486,15632,15633],{},[49,15634,15635],{},"10.100.102.109",[1470,15637,15638,15643,15646,15651],{},[1486,15639,15640],{},[1748,15641,15642],{},"BFF VM",[1486,15644,15645],{},"Public-facing Caddy proxy and Cloudflare tunnel",[1486,15647,15648],{},[49,15649,15650],{},"10.10.10.3",[1486,15652,15653],{},[49,15654,15655],{},"10.100.102.103",[1470,15657,15658,15663,15666,15671],{},[1486,15659,15660],{},[1748,15661,15662],{},"Workstation",[1486,15664,15665],{},"Local machine or browser client",[1486,15667,15668],{},[49,15669,15670],{},"10.10.10.10",[1486,15672,15673],{},[49,15674,15675],{},"10.100.102.102",[1737,15677,15679],{"id":15678},"pki-requirements","PKI Requirements",[17,15681,15682],{},"A successful mTLS deployment requires several cryptographic files. Keep private keys strictly confidential on their respective machines.",[1464,15684,15685,15699],{},[1467,15686,15687],{},[1470,15688,15689,15691,15694,15696],{},[1473,15690,7960],{},[1473,15692,15693],{},"Location",[1473,15695,6106],{},[1473,15697,15698],{},"Secret",[1481,15700,15701,15717,15738,15758,15778],{},[1470,15702,15703,15708,15711,15714],{},[1486,15704,15705],{},[49,15706,15707],{},"root.crt",[1486,15709,15710],{},"API VM, BFF VM, Workstation",[1486,15712,15713],{},"Public certificate of your private CA",[1486,15715,15716],{},"No",[1470,15718,15719,15724,15730,15733],{},[1486,15720,15721],{},[49,15722,15723],{},"root.key",[1486,15725,15726,15727,1497],{},"API VM ONLY (",[49,15728,15729],{},"/var/lib/caddy/.../root.key",[1486,15731,15732],{},"CA private key used to sign client certificates",[1486,15734,15735],{},[1748,15736,15737],{},"Yes",[1470,15739,15740,15748,15750,15753],{},[1486,15741,15742,4151,15745],{},[49,15743,15744],{},"server.key",[49,15746,15747],{},"server.crt",[1486,15749,15622],{},[1486,15751,15752],{},"Identifies the server to connecting clients",[1486,15754,15755,15757],{},[1748,15756,15737],{}," (Key)",[1470,15759,15760,15766,15773,15776],{},[1486,15761,15762,15763,1497],{},"Client Certificates (",[49,15764,15765],{},"*.crt",[1486,15767,15768,15769,15772],{},"API VM (",[49,15770,15771],{},"/etc/caddy/client-leaves/",") and Clients",[1486,15774,15775],{},"Whitelisted leaf certificates",[1486,15777,15716],{},[1470,15779,15780,15786,15789,15792],{},[1486,15781,15782,15783,1497],{},"Client Keys (",[49,15784,15785],{},"*.key",[1486,15787,15788],{},"BFF VM, Workstation",[1486,15790,15791],{},"Signs the client side of the handshake",[1486,15793,15794],{},[1748,15795,15737],{},[1804,15797,15798],{},[17,15799,15800,15801,15803,15804,15807],{},"Never distribute ",[49,15802,15723],{}," or any ",[49,15805,15806],{},".key"," file across the network. Private keys must remain on the device where they are generated.",[1737,15809,15811],{"id":15810},"caddy-configuration","Caddy Configuration",[17,15813,15814],{},"Configure Caddy on the backend to enforce mTLS verification, and configure the frontend proxy to provide the required client certificate.",[58,15816,15818],{"id":15817},"backend-server-api-vm","Backend Server (API VM)",[17,15820,15821,15822,15825],{},"Add the following configuration to the API VM's ",[49,15823,15824],{},"Caddyfile"," to enforce client certificate verification and restrict access to specific IP addresses.",[66,15827,15831],{"className":15828,"code":15829,"filename":15824,"language":15830,"meta":15,"style":15},"language-caddyfile shiki shiki-themes github-light github-dark github-dark","{\n    # Whitelist specific proxies that forward traffic\n    trusted_proxies 10.10.10.3 10.10.10.2\n    trusted_proxies_strict true\n}\n\nhttps://content.com {\n    tls internal\n\n    tls {\n        client_auth {\n            mode require_and_verify\n            \n            # Trust the root CA and specific whitelisted client certificates\n            trusted_ca_cert_file          /etc/caddy/client-leaves/root.crt\n            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi.crt\n            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi-ws.crt\n        }\n    }\n\n    encode gzip\n\n    @allowedIPs {\n        remote_ip 10.10.10.3 10.10.10.10\n    }\n    \n    handle @allowedIPs {\n        reverse_proxy 127.0.0.1:1337\n    }\n\n    handle {\n        respond \"Access Denied\" 403\n    }\n}\n","caddyfile",[49,15832,15833,15837,15842,15847,15852,15856,15860,15865,15870,15874,15879,15884,15889,15894,15899,15904,15909,15914,15919,15923,15927,15932,15936,15941,15946,15950,15954,15959,15964,15968,15972,15977,15982,15986],{"__ignoreMap":15},[75,15834,15835],{"class":77,"line":78},[75,15836,8760],{},[75,15838,15839],{"class":77,"line":25},[75,15840,15841],{},"    # Whitelist specific proxies that forward traffic\n",[75,15843,15844],{"class":77,"line":215},[75,15845,15846],{},"    trusted_proxies 10.10.10.3 10.10.10.2\n",[75,15848,15849],{"class":77,"line":221},[75,15850,15851],{},"    trusted_proxies_strict true\n",[75,15853,15854],{"class":77,"line":235},[75,15855,325],{},[75,15857,15858],{"class":77,"line":248},[75,15859,527],{"emptyLinePlaceholder":30},[75,15861,15862],{"class":77,"line":261},[75,15863,15864],{},"https://content.com {\n",[75,15866,15867],{"class":77,"line":274},[75,15868,15869],{},"    tls internal\n",[75,15871,15872],{"class":77,"line":615},[75,15873,527],{"emptyLinePlaceholder":30},[75,15875,15876],{"class":77,"line":636},[75,15877,15878],{},"    tls {\n",[75,15880,15881],{"class":77,"line":642},[75,15882,15883],{},"        client_auth {\n",[75,15885,15886],{"class":77,"line":663},[75,15887,15888],{},"            mode require_and_verify\n",[75,15890,15891],{"class":77,"line":669},[75,15892,15893],{},"            \n",[75,15895,15896],{"class":77,"line":675},[75,15897,15898],{},"            # Trust the root CA and specific whitelisted client certificates\n",[75,15900,15901],{"class":77,"line":684},[75,15902,15903],{},"            trusted_ca_cert_file          /etc/caddy/client-leaves/root.crt\n",[75,15905,15906],{"class":77,"line":690},[75,15907,15908],{},"            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi.crt\n",[75,15910,15911],{"class":77,"line":2254},[75,15912,15913],{},"            trusted_leaf_cert_file        /etc/caddy/client-leaves/strapi-ws.crt\n",[75,15915,15916],{"class":77,"line":2259},[75,15917,15918],{},"        }\n",[75,15920,15921],{"class":77,"line":2276},[75,15922,13294],{},[75,15924,15925],{"class":77,"line":2294},[75,15926,527],{"emptyLinePlaceholder":30},[75,15928,15929],{"class":77,"line":2299},[75,15930,15931],{},"    encode gzip\n",[75,15933,15934],{"class":77,"line":2308},[75,15935,527],{"emptyLinePlaceholder":30},[75,15937,15938],{"class":77,"line":2792},[75,15939,15940],{},"    @allowedIPs {\n",[75,15942,15943],{"class":77,"line":2802},[75,15944,15945],{},"        remote_ip 10.10.10.3 10.10.10.10\n",[75,15947,15948],{"class":77,"line":2810},[75,15949,13294],{},[75,15951,15952],{"class":77,"line":2815},[75,15953,218],{},[75,15955,15956],{"class":77,"line":3092},[75,15957,15958],{},"    handle @allowedIPs {\n",[75,15960,15961],{"class":77,"line":3103},[75,15962,15963],{},"        reverse_proxy 127.0.0.1:1337\n",[75,15965,15966],{"class":77,"line":3114},[75,15967,13294],{},[75,15969,15970],{"class":77,"line":3120},[75,15971,527],{"emptyLinePlaceholder":30},[75,15973,15974],{"class":77,"line":3126},[75,15975,15976],{},"    handle {\n",[75,15978,15979],{"class":77,"line":3147},[75,15980,15981],{},"        respond \"Access Denied\" 403\n",[75,15983,15984],{"class":77,"line":5604},[75,15985,13294],{},[75,15987,15988],{"class":77,"line":5610},[75,15989,325],{},[58,15991,15993],{"id":15992},"frontend-proxy-bff-vm","Frontend Proxy (BFF VM)",[17,15995,15996],{},"Configure the proxy to present a client certificate when forwarding requests to the secure backend.",[66,15998,16000],{"className":15828,"code":15999,"filename":15824,"language":15830,"meta":15,"style":15},"http://127.0.0.1:8080 {\n    reverse_proxy https://10.10.10.2:443 {\n        transport http {\n            tls_server_name content.com\n            tls_trusted_ca_certs /var/lib/caddy/mtls/root.crt\n            tls_client_auth      /var/lib/caddy/mtls/strapi.crt /var/lib/caddy/mtls/strapi.key\n        }\n    }\n}\n",[49,16001,16002,16007,16012,16017,16022,16027,16032,16036,16040],{"__ignoreMap":15},[75,16003,16004],{"class":77,"line":78},[75,16005,16006],{},"http://127.0.0.1:8080 {\n",[75,16008,16009],{"class":77,"line":25},[75,16010,16011],{},"    reverse_proxy https://10.10.10.2:443 {\n",[75,16013,16014],{"class":77,"line":215},[75,16015,16016],{},"        transport http {\n",[75,16018,16019],{"class":77,"line":221},[75,16020,16021],{},"            tls_server_name content.com\n",[75,16023,16024],{"class":77,"line":235},[75,16025,16026],{},"            tls_trusted_ca_certs /var/lib/caddy/mtls/root.crt\n",[75,16028,16029],{"class":77,"line":248},[75,16030,16031],{},"            tls_client_auth      /var/lib/caddy/mtls/strapi.crt /var/lib/caddy/mtls/strapi.key\n",[75,16033,16034],{"class":77,"line":261},[75,16035,15918],{},[75,16037,16038],{"class":77,"line":274},[75,16039,13294],{},[75,16041,16042],{"class":77,"line":615},[75,16043,325],{},[7024,16045,16046],{},[17,16047,16048,16049,1802],{},"If using a tunnel like Cloudflared, direct it to ",[49,16050,16051],{},"http://127.0.0.1:8080",[1737,16053,16055],{"id":16054},"client-certificate-operations","Client Certificate Operations",[17,16057,16058],{},"Generate a new client certificate for each workstation or service that needs access to the API.",[58,16060,16062],{"id":16061},"_1-create-a-key-and-csr-workstation","1. Create a Key and CSR (Workstation)",[17,16064,16065],{},"Generate a private key and a Certificate Signing Request (CSR) on the client machine.",[66,16067,16069],{"className":68,"code":16068,"filename":70,"language":71,"meta":15,"style":15},"# Generate a 4096-bit RSA private key\nopenssl genpkey -algorithm RSA -out ~/client.key -pkeyopt rsa_keygen_bits:4096\n\n# Create a certificate signing request\nopenssl req -new -key ~/client.key -subj \"/CN=workstation-client\" -out ~/client.csr\n\n# Transfer the CSR to the API VM for signing\nscp ~/client.csr user@api-vm:/tmp/\n",[49,16070,16071,16076,16100,16104,16109,16135,16139,16144],{"__ignoreMap":15},[75,16072,16073],{"class":77,"line":78},[75,16074,16075],{"class":2542},"# Generate a 4096-bit RSA private key\n",[75,16077,16078,16080,16083,16086,16089,16091,16094,16097],{"class":77,"line":25},[75,16079,9620],{"class":190},[75,16081,16082],{"class":92}," genpkey",[75,16084,16085],{"class":171}," -algorithm",[75,16087,16088],{"class":92}," RSA",[75,16090,12585],{"class":171},[75,16092,16093],{"class":92}," ~/client.key",[75,16095,16096],{"class":171}," -pkeyopt",[75,16098,16099],{"class":92}," rsa_keygen_bits:4096\n",[75,16101,16102],{"class":77,"line":215},[75,16103,527],{"emptyLinePlaceholder":30},[75,16105,16106],{"class":77,"line":221},[75,16107,16108],{"class":2542},"# Create a certificate signing request\n",[75,16110,16111,16113,16116,16119,16122,16124,16127,16130,16132],{"class":77,"line":235},[75,16112,9620],{"class":190},[75,16114,16115],{"class":92}," req",[75,16117,16118],{"class":171}," -new",[75,16120,16121],{"class":171}," -key",[75,16123,16093],{"class":92},[75,16125,16126],{"class":171}," -subj",[75,16128,16129],{"class":92}," \"/CN=workstation-client\"",[75,16131,12585],{"class":171},[75,16133,16134],{"class":92}," ~/client.csr\n",[75,16136,16137],{"class":77,"line":248},[75,16138,527],{"emptyLinePlaceholder":30},[75,16140,16141],{"class":77,"line":261},[75,16142,16143],{"class":2542},"# Transfer the CSR to the API VM for signing\n",[75,16145,16146,16149,16152],{"class":77,"line":274},[75,16147,16148],{"class":190},"scp",[75,16150,16151],{"class":92}," ~/client.csr",[75,16153,16154],{"class":92}," user@api-vm:/tmp/\n",[58,16156,16158],{"id":16157},"_2-sign-the-certificate-api-vm","2. Sign the Certificate (API VM)",[17,16160,16161],{},"Use the Caddy internal CA to sign the client's CSR. This creates a valid client certificate.",[66,16163,16165],{"className":68,"code":16164,"filename":70,"language":71,"meta":15,"style":15},"CA_DIR=/var/lib/caddy/.local/share/caddy/pki/authorities/local\nCSR=/tmp/client.csr\nCRT=/etc/caddy/client-leaves/client.crt\n\n# Sign the CSR to issue a 1-year certificate\nsudo openssl x509 -req -in $CSR -CA $CA_DIR/root.crt -CAkey $CA_DIR/root.key \\\n  -CAcreateserial -out $CRT -days 365 -sha256\n\n# Fix ownership and permissions\nsudo chown caddy:caddy $CRT\nsudo chmod 644 $CRT\n\n# Reload Caddy to recognize the new certificate\nsudo caddy reload --config /etc/caddy/Caddyfile\n",[49,16166,16167,16177,16187,16197,16201,16206,16245,16264,16268,16273,16286,16298,16302,16307],{"__ignoreMap":15},[75,16168,16169,16172,16174],{"class":77,"line":78},[75,16170,16171],{"class":85},"CA_DIR",[75,16173,89],{"class":81},[75,16175,16176],{"class":92},"/var/lib/caddy/.local/share/caddy/pki/authorities/local\n",[75,16178,16179,16182,16184],{"class":77,"line":25},[75,16180,16181],{"class":85},"CSR",[75,16183,89],{"class":81},[75,16185,16186],{"class":92},"/tmp/client.csr\n",[75,16188,16189,16192,16194],{"class":77,"line":215},[75,16190,16191],{"class":85},"CRT",[75,16193,89],{"class":81},[75,16195,16196],{"class":92},"/etc/caddy/client-leaves/client.crt\n",[75,16198,16199],{"class":77,"line":221},[75,16200,527],{"emptyLinePlaceholder":30},[75,16202,16203],{"class":77,"line":235},[75,16204,16205],{"class":2542},"# Sign the CSR to issue a 1-year certificate\n",[75,16207,16208,16211,16214,16217,16220,16223,16226,16229,16232,16235,16238,16240,16243],{"class":77,"line":248},[75,16209,16210],{"class":190},"sudo",[75,16212,16213],{"class":92}," openssl",[75,16215,16216],{"class":92}," x509",[75,16218,16219],{"class":171}," -req",[75,16221,16222],{"class":171}," -in",[75,16224,16225],{"class":85}," $CSR ",[75,16227,16228],{"class":171},"-CA",[75,16230,16231],{"class":85}," $CA_DIR",[75,16233,16234],{"class":92},"/root.crt",[75,16236,16237],{"class":171}," -CAkey",[75,16239,16231],{"class":85},[75,16241,16242],{"class":92},"/root.key",[75,16244,850],{"class":171},[75,16246,16247,16250,16252,16255,16258,16261],{"class":77,"line":261},[75,16248,16249],{"class":171},"  -CAcreateserial",[75,16251,12585],{"class":171},[75,16253,16254],{"class":85}," $CRT ",[75,16256,16257],{"class":171},"-days",[75,16259,16260],{"class":171}," 365",[75,16262,16263],{"class":171}," -sha256\n",[75,16265,16266],{"class":77,"line":274},[75,16267,527],{"emptyLinePlaceholder":30},[75,16269,16270],{"class":77,"line":615},[75,16271,16272],{"class":2542},"# Fix ownership and permissions\n",[75,16274,16275,16277,16280,16283],{"class":77,"line":636},[75,16276,16210],{"class":190},[75,16278,16279],{"class":92}," chown",[75,16281,16282],{"class":92}," caddy:caddy",[75,16284,16285],{"class":85}," $CRT\n",[75,16287,16288,16290,16293,16296],{"class":77,"line":642},[75,16289,16210],{"class":190},[75,16291,16292],{"class":92}," chmod",[75,16294,16295],{"class":171}," 644",[75,16297,16285],{"class":85},[75,16299,16300],{"class":77,"line":663},[75,16301,527],{"emptyLinePlaceholder":30},[75,16303,16304],{"class":77,"line":669},[75,16305,16306],{"class":2542},"# Reload Caddy to recognize the new certificate\n",[75,16308,16309,16311,16314,16317,16320],{"class":77,"line":675},[75,16310,16210],{"class":190},[75,16312,16313],{"class":92}," caddy",[75,16315,16316],{"class":92}," reload",[75,16318,16319],{"class":171}," --config",[75,16321,16322],{"class":92}," /etc/caddy/Caddyfile\n",[58,16324,16326],{"id":16325},"_3-import-the-certificate-workstation","3. Import the Certificate (Workstation)",[17,16328,16329,16330,16333],{},"Transfer the signed certificate back to the workstation and bundle it into a PKCS#12 (",[49,16331,16332],{},".p12",") file for browser installation.",[66,16335,16337],{"className":68,"code":16336,"filename":70,"language":71,"meta":15,"style":15},"# Fetch the signed certificate from the API VM\nscp user@api-vm:/etc/caddy/client-leaves/client.crt ~/\n\n# Bundle the key and certificate (you will be prompted to set an export password)\nopenssl pkcs12 -export -inkey ~/client.key -in ~/client.crt -certfile ~/root.crt -name \"Secure-Client\" -out ~/client.p12\n\n# Clean up the raw files after importing the bundle into your browser\nshred -u ~/client.key ~/client.crt ~/client.p12\n",[49,16338,16339,16344,16354,16358,16363,16400,16404,16409],{"__ignoreMap":15},[75,16340,16341],{"class":77,"line":78},[75,16342,16343],{"class":2542},"# Fetch the signed certificate from the API VM\n",[75,16345,16346,16348,16351],{"class":77,"line":25},[75,16347,16148],{"class":190},[75,16349,16350],{"class":92}," user@api-vm:/etc/caddy/client-leaves/client.crt",[75,16352,16353],{"class":92}," ~/\n",[75,16355,16356],{"class":77,"line":215},[75,16357,527],{"emptyLinePlaceholder":30},[75,16359,16360],{"class":77,"line":221},[75,16361,16362],{"class":2542},"# Bundle the key and certificate (you will be prompted to set an export password)\n",[75,16364,16365,16367,16370,16373,16376,16378,16380,16383,16386,16389,16392,16395,16397],{"class":77,"line":235},[75,16366,9620],{"class":190},[75,16368,16369],{"class":92}," pkcs12",[75,16371,16372],{"class":171}," -export",[75,16374,16375],{"class":171}," -inkey",[75,16377,16093],{"class":92},[75,16379,16222],{"class":171},[75,16381,16382],{"class":92}," ~/client.crt",[75,16384,16385],{"class":171}," -certfile",[75,16387,16388],{"class":92}," ~/root.crt",[75,16390,16391],{"class":171}," -name",[75,16393,16394],{"class":92}," \"Secure-Client\"",[75,16396,12585],{"class":171},[75,16398,16399],{"class":92}," ~/client.p12\n",[75,16401,16402],{"class":77,"line":248},[75,16403,527],{"emptyLinePlaceholder":30},[75,16405,16406],{"class":77,"line":261},[75,16407,16408],{"class":2542},"# Clean up the raw files after importing the bundle into your browser\n",[75,16410,16411,16414,16416,16418,16420],{"class":77,"line":274},[75,16412,16413],{"class":190},"shred",[75,16415,8329],{"class":171},[75,16417,16093],{"class":92},[75,16419,16382],{"class":92},[75,16421,16399],{"class":92},[1852,16423,16424],{},[17,16425,16426,16427,16430,16431,16433],{},"To revoke access, simply delete the client's ",[49,16428,16429],{},".crt"," file from ",[49,16432,15771],{}," on the API VM and reload Caddy.",[1737,16435,16437],{"id":16436},"network-security","Network Security",[17,16439,16440],{},"Restrict network traffic using UFW and WireGuard to ensure clients can only connect via authorized paths.",[58,16442,16444],{"id":16443},"ufw-rules","UFW Rules",[17,16446,16447],{},"Configure the firewall on each node to drop unexpected traffic.",[66,16449,16451],{"className":68,"code":16450,"filename":70,"language":71,"meta":15,"style":15},"# Allow MySQL access strictly from the API VM\nsudo ufw allow in on wg0 proto tcp from 10.10.10.2 to any port 9000 comment 'MySQL from API'\n\n# Allow incoming HTTPS traffic on the API VM from the proxy\nsudo ufw allow in proto tcp from 10.10.10.3 to any port 443 comment 'HTTPS from BFF'\n",[49,16452,16453,16458,16502,16506,16511],{"__ignoreMap":15},[75,16454,16455],{"class":77,"line":78},[75,16456,16457],{"class":2542},"# Allow MySQL access strictly from the API VM\n",[75,16459,16460,16462,16465,16468,16470,16473,16476,16478,16480,16483,16486,16488,16491,16493,16496,16499],{"class":77,"line":25},[75,16461,16210],{"class":190},[75,16463,16464],{"class":92}," ufw",[75,16466,16467],{"class":92}," allow",[75,16469,11644],{"class":92},[75,16471,16472],{"class":92}," on",[75,16474,16475],{"class":92}," wg0",[75,16477,509],{"class":92},[75,16479,7374],{"class":92},[75,16481,16482],{"class":92}," from",[75,16484,16485],{"class":171}," 10.10.10.2",[75,16487,11908],{"class":92},[75,16489,16490],{"class":92}," any",[75,16492,496],{"class":92},[75,16494,16495],{"class":171}," 9000",[75,16497,16498],{"class":92}," comment",[75,16500,16501],{"class":92}," 'MySQL from API'\n",[75,16503,16504],{"class":77,"line":215},[75,16505,527],{"emptyLinePlaceholder":30},[75,16507,16508],{"class":77,"line":221},[75,16509,16510],{"class":2542},"# Allow incoming HTTPS traffic on the API VM from the proxy\n",[75,16512,16513,16515,16517,16519,16521,16523,16525,16527,16530,16532,16534,16536,16539,16541],{"class":77,"line":235},[75,16514,16210],{"class":190},[75,16516,16464],{"class":92},[75,16518,16467],{"class":92},[75,16520,11644],{"class":92},[75,16522,509],{"class":92},[75,16524,7374],{"class":92},[75,16526,16482],{"class":92},[75,16528,16529],{"class":171}," 10.10.10.3",[75,16531,11908],{"class":92},[75,16533,16490],{"class":92},[75,16535,496],{"class":92},[75,16537,16538],{"class":171}," 443",[75,16540,16498],{"class":92},[75,16542,16543],{"class":92}," 'HTTPS from BFF'\n",[58,16545,16547],{"id":16546},"express-application-setup","Express Application Setup",[17,16549,16550],{},"If you run an Express backend behind Caddy, configure the application to trust the proxies forwarding the traffic.",[66,16552,16557],{"className":16553,"code":16554,"filename":16555,"language":16556,"meta":15,"style":15},"language-typescript shiki shiki-themes github-light github-dark github-dark","// Trust the local loopback and the BFF VM proxy\napp.set('trust proxy', (ip: string) => {\n  return ip === '127.0.0.1' || ip === '10.10.10.3';\n});\n","app.ts","typescript",[49,16558,16559,16564,16590,16613],{"__ignoreMap":15},[75,16560,16561],{"class":77,"line":78},[75,16562,16563],{"class":2542},"// Trust the local loopback and the BFF VM proxy\n",[75,16565,16566,16568,16570,16572,16575,16577,16580,16582,16584,16586,16588],{"class":77,"line":25},[75,16567,2048],{"class":85},[75,16569,10357],{"class":190},[75,16571,2135],{"class":85},[75,16573,16574],{"class":92},"'trust proxy'",[75,16576,10488],{"class":85},[75,16578,16579],{"class":501},"ip",[75,16581,1883],{"class":81},[75,16583,6509],{"class":171},[75,16585,2070],{"class":85},[75,16587,2073],{"class":81},[75,16589,2076],{"class":85},[75,16591,16592,16594,16597,16599,16602,16604,16606,16608,16611],{"class":77,"line":215},[75,16593,6598],{"class":81},[75,16595,16596],{"class":85}," ip ",[75,16598,2267],{"class":81},[75,16600,16601],{"class":92}," '127.0.0.1'",[75,16603,200],{"class":81},[75,16605,16596],{"class":85},[75,16607,2267],{"class":81},[75,16609,16610],{"class":92}," '10.10.10.3'",[75,16612,2605],{"class":85},[75,16614,16615],{"class":77,"line":221},[75,16616,2311],{"class":85},[1737,16618,16620],{"id":16619},"toolbox-and-verification","Toolbox and Verification",[17,16622,16623],{},"Use these commands to troubleshoot and verify your mTLS configuration.",[1464,16625,16626,16636],{},[1467,16627,16628],{},[1470,16629,16630,16633],{},[1473,16631,16632],{},"Task",[1473,16634,16635],{},"Command",[1481,16637,16638,16653,16663,16673],{},[1470,16639,16640,16643],{},[1486,16641,16642],{},"Verify key and certificate match",[1486,16644,16645,16648,16650],{},[49,16646,16647],{},"openssl rsa -in key.pem -noout -modulus | openssl md5",[6038,16649],{},[49,16651,16652],{},"openssl x509 -in cert.pem -noout -modulus | openssl md5",[1470,16654,16655,16658],{},[1486,16656,16657],{},"Test connection manually",[1486,16659,16660],{},[49,16661,16662],{},"openssl s_client -connect content.com:443 -cert client.crt -key client.key -CAfile root.crt",[1470,16664,16665,16668],{},[1486,16666,16667],{},"Validate Caddyfile syntax",[1486,16669,16670],{},[49,16671,16672],{},"sudo caddy validate --config /etc/caddy/Caddyfile",[1470,16674,16675,16678],{},[1486,16676,16677],{},"Format Caddyfile",[1486,16679,16680],{},[49,16681,16682],{},"sudo caddy fmt --overwrite /etc/caddy/Caddyfile",[17,16684,16685],{},"To quickly test authentication from the command line:",[66,16687,16689],{"className":68,"code":16688,"filename":70,"language":71,"meta":15,"style":15},"# Should succeed and return HTTP 200\ncurl -sv https://content.com/admin --cert ~/client.crt --key ~/client.key --cacert ~/root.crt -o /dev/null\n\n# Should fail and return an OpenSSL alert (certificate required)\ncurl -k https://content.com/admin\n",[49,16690,16691,16696,16726,16730,16735],{"__ignoreMap":15},[75,16692,16693],{"class":77,"line":78},[75,16694,16695],{"class":2542},"# Should succeed and return HTTP 200\n",[75,16697,16698,16700,16703,16706,16709,16711,16714,16716,16719,16721,16723],{"class":77,"line":25},[75,16699,1504],{"class":190},[75,16701,16702],{"class":171}," -sv",[75,16704,16705],{"class":92}," https://content.com/admin",[75,16707,16708],{"class":171}," --cert",[75,16710,16382],{"class":92},[75,16712,16713],{"class":171}," --key",[75,16715,16093],{"class":92},[75,16717,16718],{"class":171}," --cacert",[75,16720,16388],{"class":92},[75,16722,7659],{"class":171},[75,16724,16725],{"class":92}," /dev/null\n",[75,16727,16728],{"class":77,"line":215},[75,16729,527],{"emptyLinePlaceholder":30},[75,16731,16732],{"class":77,"line":221},[75,16733,16734],{"class":2542},"# Should fail and return an OpenSSL alert (certificate required)\n",[75,16736,16737,16739,16742],{"class":77,"line":235},[75,16738,1504],{"class":190},[75,16740,16741],{"class":171}," -k",[75,16743,16744],{"class":92}," https://content.com/admin\n",[1697,16746,16747],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":15,"searchDepth":25,"depth":25,"links":16749},[16750,16751,16752,16756,16761,16765],{"id":15574,"depth":25,"text":15575},{"id":15678,"depth":25,"text":15679},{"id":15810,"depth":25,"text":15811,"children":16753},[16754,16755],{"id":15817,"depth":215,"text":15818},{"id":15992,"depth":215,"text":15993},{"id":16054,"depth":25,"text":16055,"children":16757},[16758,16759,16760],{"id":16061,"depth":215,"text":16062},{"id":16157,"depth":215,"text":16158},{"id":16325,"depth":215,"text":16326},{"id":16436,"depth":25,"text":16437,"children":16762},[16763,16764],{"id":16443,"depth":215,"text":16444},{"id":16546,"depth":215,"text":16547},{"id":16619,"depth":25,"text":16620},"tutorial","A comprehensive cookbook for configuring Mutual TLS (mTLS) authentication using the Caddy web server.","/knowledge-base/mtls.jpeg",{},"/knowledge-base/mtls","10",{"title":15566,"description":16767},{"loc":16770},"knowledge-base/mtls",[16776,16777,2386,7935],"mTLS","Caddy","XGsX7vWhgA6LfeASuS0DhXjPUgCluKg2MB8spCONa6A",{"id":16780,"title":16781,"body":16782,"category":7926,"contentType":8539,"date":1712,"description":17660,"extension":28,"head":27,"image":17661,"meta":17662,"navigation":30,"ogImage":27,"path":17663,"readingTime":1717,"robots":27,"schemaOrg":27,"seo":17664,"sitemap":17665,"stem":17666,"tags":17667,"__hash__":17669},"knowledgeBase/knowledge-base/ssh-recipe.md","SSH CookBook",{"type":7,"value":16783,"toc":17638},[16784,16788,16791,16800,16803,16821,16824,16829,16836,16839,16844,16849,16852,16858,16862,16865,16870,16875,16877,16881,16887,16904,16906,16923,16926,16930,16935,16946,16950,16956,16959,16971,16974,16977,16989,16991,16995,17030,17032,17036,17058,17060,17064,17067,17080,17087,17101,17104,17116,17119,17122,17124,17128,17131,17136,17143,17146,17148,17152,17156,17172,17176,17207,17211,17229,17231,17247,17251,17256,17259,17268,17270,17283,17286,17291,17293,17297,17303,17316,17318,17322,17326,17329,17341,17346,17352,17355,17367,17369,17373,17376,17380,17420,17424,17434,17437,17467,17470,17521,17524,17534,17538,17540,17555,17561,17564,17578,17581,17587,17590,17593,17622,17625,17635],[1737,16785,16787],{"id":16786},"generating-keys","GENERATING KEYS",[17,16789,16790],{},"To generate an RSA key pair on your local computer, type:",[66,16792,16794],{"className":68,"code":16793,"language":71,"meta":15,"style":15},"ssh-keygen\n",[49,16795,16796],{"__ignoreMap":15},[75,16797,16798],{"class":77,"line":78},[75,16799,16793],{"class":190},[17,16801,16802],{},"OR",[66,16804,16806],{"className":68,"code":16805,"language":71,"meta":15,"style":15},"ssh-keygen -b 4096   # For longer key (4096 bits)\n",[49,16807,16808],{"__ignoreMap":15},[75,16809,16810,16813,16815,16818],{"class":77,"line":78},[75,16811,16812],{"class":190},"ssh-keygen",[75,16814,194],{"class":171},[75,16816,16817],{"class":171}," 4096",[75,16819,16820],{"class":2542},"   # For longer key (4096 bits)\n",[17,16822,16823],{},"You will see:",[3960,16825,16826],{},[17,16827,16828],{},"\"Generating public/private rsa key pair.\nEnter file in which to save the key (/home/demo/.ssh/id_rsa):\"",[17,16830,16831,16832,16835],{},"This prompt allows you to choose the location to store your RSA private key. Press ENTER to leave this as the default, which will store them in the ",[49,16833,16834],{},".ssh"," hidden directory in your user’s home directory.",[17,16837,16838],{},"Next, you'll be prompted to add a passphrase:",[3960,16840,16841],{},[17,16842,16843],{},"\"Enter passphrase (empty for no passphrase):\"\n\"Enter same passphrase again:\"",[3960,16845,16846],{},[17,16847,16848],{},"⚠️  Setting a passphrase adds extra security. Without it, anyone with your private key can access your servers.",[17,16850,16851],{},"SSH key pair files generated:",[66,16853,16856],{"className":16854,"code":16855,"language":9154},[9152],"~/.ssh/id_rsa       → Private key (DO NOT SHARE)\n~/.ssh/id_rsa.pub   → Public key (can be shared)\n",[49,16857,16855],{"__ignoreMap":15},[58,16859,16861],{"id":16860},"overwrite-warning","OVERWRITE WARNING",[17,16863,16864],{},"If a key already exists, you will see:",[3960,16866,16867],{},[17,16868,16869],{},"\"Overwrite (y/n)?\"",[3960,16871,16872],{},[17,16873,16874],{},"Choose 'y' only if you're sure. Overwriting removes access to all servers using the old key!",[1414,16876],{},[1737,16878,16880],{"id":16879},"transferring-public-key-to-server","TRANSFERRING PUBLIC KEY TO SERVER",[17,16882,2857,16883,16886],{},[49,16884,16885],{},"ssh-copy-id"," to add your public key to the remote server:",[66,16888,16890],{"className":68,"code":16889,"language":71,"meta":15,"style":15},"ssh-copy-id -i ~/.ssh/id_rsa.pub remoteUser@ServerIP\n",[49,16891,16892],{"__ignoreMap":15},[75,16893,16894,16896,16898,16901],{"class":77,"line":78},[75,16895,16885],{"class":190},[75,16897,7498],{"class":171},[75,16899,16900],{"class":92}," ~/.ssh/id_rsa.pub",[75,16902,16903],{"class":92}," remoteUser@ServerIP\n",[17,16905,16802],{},[66,16907,16909],{"className":68,"code":16908,"language":71,"meta":15,"style":15},"ssh-copy-id -i path/to/public/key remoteServerUser@ServerIP\n",[49,16910,16911],{"__ignoreMap":15},[75,16912,16913,16915,16917,16920],{"class":77,"line":78},[75,16914,16885],{"class":190},[75,16916,7498],{"class":171},[75,16918,16919],{"class":92}," path/to/public/key",[75,16921,16922],{"class":92}," remoteServerUser@ServerIP\n",[17,16924,16925],{},"This will add the public key to the remote server.",[58,16927,16929],{"id":16928},"a-prompt-that-should-appear-only-once","A prompt THAT SHOULD APPEAR ONLY ONCE:",[3960,16931,16932],{},[17,16933,16934],{},"\"The authenticity of host '111.111.11.111 (111.111.11.111)' can't be established.\"\n\"ECDSA key fingerprint is fd:fd:d4:f9:77:fe:73:84:e1:55:00:ad:d6:6d:22:fe.\"\n\"Are you sure you want to continue connecting (yes/no)? yes\"\n\"/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed\"\n\"/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys\"\n\"demo@111.111.11.111's password:\"",[17,16936,16937,16938,16941,16942,16945],{},"After typing in the password, the contents of your ",[49,16939,16940],{},"~/.ssh/id_rsa.pub"," key will be appended to the end of the user account’s ",[49,16943,16944],{},"~/.ssh/authorized_keys"," file:",[17,16947,16948],{},[1748,16949,4216],{},[66,16951,16954],{"className":16952,"code":16953,"language":9154},[9152],"Number of key(s) added: 1\n",[49,16955,16953],{"__ignoreMap":15},[17,16957,16958],{},"Now try logging into the machine, with:",[66,16960,16962],{"className":68,"code":16961,"language":71,"meta":15,"style":15},"ssh demo@111.111.11.111\n",[49,16963,16964],{"__ignoreMap":15},[75,16965,16966,16968],{"class":77,"line":78},[75,16967,1692],{"class":190},[75,16969,16970],{"class":92}," demo@111.111.11.111\n",[17,16972,16973],{},"and check to make sure that only the key(s) you wanted were added.",[17,16975,16976],{},"You can now log in to that account without a password:",[66,16978,16980],{"className":68,"code":16979,"language":71,"meta":15,"style":15},"ssh username@remote_host\n",[49,16981,16982],{"__ignoreMap":15},[75,16983,16984,16986],{"class":77,"line":78},[75,16985,1692],{"class":190},[75,16987,16988],{"class":92}," username@remote_host\n",[1414,16990],{},[1737,16992,16994],{"id":16993},"permissions-to-be-set-on-the-local-host","Permissions To be Set On the Local HOST",[66,16996,16998],{"className":68,"code":16997,"language":71,"meta":15,"style":15},"chmod 600 /path/to/PRIVATEKEY\nchmod 644 /path/to/publickey\nchmod 700 ~/.ssh\n",[49,16999,17000,17011,17020],{"__ignoreMap":15},[75,17001,17002,17005,17008],{"class":77,"line":78},[75,17003,17004],{"class":190},"chmod",[75,17006,17007],{"class":171}," 600",[75,17009,17010],{"class":92}," /path/to/PRIVATEKEY\n",[75,17012,17013,17015,17017],{"class":77,"line":25},[75,17014,17004],{"class":190},[75,17016,16295],{"class":171},[75,17018,17019],{"class":92}," /path/to/publickey\n",[75,17021,17022,17024,17027],{"class":77,"line":215},[75,17023,17004],{"class":190},[75,17025,17026],{"class":171}," 700",[75,17028,17029],{"class":92}," ~/.ssh\n",[1414,17031],{},[1737,17033,17035],{"id":17034},"permissions-to-be-set-on-the-remote-host","Permissions To be Set on the Remote HOST",[66,17037,17039],{"className":68,"code":17038,"language":71,"meta":15,"style":15},"chmod 700 ~/.ssh\nchmod 600 ~/.ssh/authorized_keys\n",[49,17040,17041,17049],{"__ignoreMap":15},[75,17042,17043,17045,17047],{"class":77,"line":78},[75,17044,17004],{"class":190},[75,17046,17026],{"class":171},[75,17048,17029],{"class":92},[75,17050,17051,17053,17055],{"class":77,"line":25},[75,17052,17004],{"class":190},[75,17054,17007],{"class":171},[75,17056,17057],{"class":92}," ~/.ssh/authorized_keys\n",[1414,17059],{},[1737,17061,17063],{"id":17062},"copying-the-key-manually","Copying The Key manually",[17,17065,17066],{},"Copy the public key:",[66,17068,17070],{"className":68,"code":17069,"language":71,"meta":15,"style":15},"cat ~/.ssh/id_rsa.pub\n",[49,17071,17072],{"__ignoreMap":15},[75,17073,17074,17077],{"class":77,"line":78},[75,17075,17076],{"class":190},"cat",[75,17078,17079],{"class":92}," ~/.ssh/id_rsa.pub\n",[17,17081,17082,17083,17086],{},"On the remote server, create the ",[49,17084,17085],{},"~/.ssh"," directory if it does not already exist:",[66,17088,17090],{"className":68,"code":17089,"language":71,"meta":15,"style":15},"mkdir -p ~/.ssh\n",[49,17091,17092],{"__ignoreMap":15},[75,17093,17094,17097,17099],{"class":77,"line":78},[75,17095,17096],{"class":190},"mkdir",[75,17098,7371],{"class":171},[75,17100,17029],{"class":92},[17,17102,17103],{},"Go to:",[66,17105,17107],{"className":68,"code":17106,"language":71,"meta":15,"style":15},"nano ~/.ssh/authorized_keys\n",[49,17108,17109],{"__ignoreMap":15},[75,17110,17111,17114],{"class":77,"line":78},[75,17112,17113],{"class":190},"nano",[75,17115,17057],{"class":92},[17,17117,17118],{},"And paste the public key.",[17,17120,17121],{},"You should now be able to log in to the remote server without a password.",[1414,17123],{},[1737,17125,17127],{"id":17126},"first-time-connecting-to-a-new-host","First Time Connecting to a New Host",[17,17129,17130],{},"Your first time connecting to a new host, you will see a message that looks like this:",[3960,17132,17133],{},[17,17134,17135],{},"\"The authenticity of host '111.111.11.111 (111.111.11.111)' can't be established.\"\n\"ECDSA key fingerprint is fd:fd:d4:f9:77:fe:73:84:e1:55:00:ad:d6:6d:22:fe.\"\n\"Are you sure you want to continue connecting (yes/no)? yes\"",[17,17137,17138,17139,17142],{},"Type ",[49,17140,17141],{},"yes"," to accept the authenticity of the remote host.",[17,17144,17145],{},"If you are using password authentication, you will be prompted for the password for the remote account here. If you are using SSH keys, you will be prompted for your private key’s passphrase if one is set, otherwise you will be logged in automatically.",[1414,17147],{},[1737,17149,17151],{"id":17150},"useful-commands","Useful commands",[58,17153,17155],{"id":17154},"logging-in-to-a-server-with-a-different-port","Logging in to a Server with a Different Port",[66,17157,17159],{"className":68,"code":17158,"language":71,"meta":15,"style":15},"ssh -p port_num username@remote_host\n",[49,17160,17161],{"__ignoreMap":15},[75,17162,17163,17165,17167,17170],{"class":77,"line":78},[75,17164,1692],{"class":190},[75,17166,7371],{"class":171},[75,17168,17169],{"class":92}," port_num",[75,17171,16988],{"class":92},[58,17173,17175],{"id":17174},"viewing-logs","Viewing Logs",[66,17177,17179],{"className":68,"code":17178,"language":71,"meta":15,"style":15},"sudo tail -n 10 -f /var/log/auth.log\ngrep sshd /var/log/auth.log\n",[49,17180,17181,17198],{"__ignoreMap":15},[75,17182,17183,17185,17188,17190,17193,17195],{"class":77,"line":78},[75,17184,16210],{"class":190},[75,17186,17187],{"class":92}," tail",[75,17189,764],{"class":171},[75,17191,17192],{"class":171}," 10",[75,17194,770],{"class":171},[75,17196,17197],{"class":92}," /var/log/auth.log\n",[75,17199,17200,17202,17205],{"class":77,"line":25},[75,17201,143],{"class":190},[75,17203,17204],{"class":92}," sshd",[75,17206,17197],{"class":92},[58,17208,17210],{"id":17209},"adding-your-ssh-keys-to-an-ssh-agent-to-avoid-typing-the-passphrase","Adding your SSH Keys to an SSH Agent to Avoid Typing the Passphrase",[66,17212,17214],{"className":68,"code":17213,"language":71,"meta":15,"style":15},"eval \"$(ssh-agent -s)\"\n",[49,17215,17216],{"__ignoreMap":15},[75,17217,17218,17220,17222,17225,17227],{"class":77,"line":78},[75,17219,184],{"class":171},[75,17221,187],{"class":92},[75,17223,17224],{"class":190},"ssh-agent",[75,17226,842],{"class":171},[75,17228,212],{"class":92},[17,17230,16802],{},[66,17232,17234],{"className":68,"code":17233,"language":71,"meta":15,"style":15},"eval $(ssh-agent)\n",[49,17235,17236],{"__ignoreMap":15},[75,17237,17238,17240,17243,17245],{"class":77,"line":78},[75,17239,184],{"class":171},[75,17241,17242],{"class":85}," $(",[75,17244,17224],{"class":190},[75,17246,585],{"class":85},[17,17248,17249],{},[1748,17250,4216],{},[3960,17252,17253],{},[17,17254,17255],{},"\"Agent pid 10891\"",[17,17257,17258],{},"This will start the agent program and place it into the background. Now, you need to add your private key to the agent, so that it can manage your key:",[66,17260,17262],{"className":68,"code":17261,"language":71,"meta":15,"style":15},"ssh-add\n",[49,17263,17264],{"__ignoreMap":15},[75,17265,17266],{"class":77,"line":78},[75,17267,17261],{"class":190},[17,17269,16802],{},[66,17271,17273],{"className":68,"code":17272,"language":71,"meta":15,"style":15},"ssh-add /path/to/privatekey\n",[49,17274,17275],{"__ignoreMap":15},[75,17276,17277,17280],{"class":77,"line":78},[75,17278,17279],{"class":190},"ssh-add",[75,17281,17282],{"class":92}," /path/to/privatekey\n",[17,17284,17285],{},"You will have to enter your passphrase (if one is set). Afterwards, your identity file is added to the agent, allowing you to use your key to sign in without having to re-enter the passphrase again.",[3960,17287,17288],{},[17,17289,17290],{},"\"Enter passphrase for /home/demo/.ssh/id_rsa:\"\n\"Identity added: /home/demo/.ssh/id_rsa (/home/demo/.ssh/id_rsa)\"",[1414,17292],{},[1737,17294,17296],{"id":17295},"agent-forwarding-for-multiple-hops","Agent Forwarding for Multiple Hops",[17,17298,17299,17300,17302],{},"To start, you must have your SSH agent started and your SSH key added to the agent (see earlier). After this is done, you need to connect to your first server using the ",[49,17301,7236],{}," option. This forwards your credentials to the server for this session:",[66,17304,17306],{"className":68,"code":17305,"language":71,"meta":15,"style":15},"ssh -A username@remote_host\n",[49,17307,17308],{"__ignoreMap":15},[75,17309,17310,17312,17314],{"class":77,"line":78},[75,17311,1692],{"class":190},[75,17313,7177],{"class":171},[75,17315,16988],{"class":92},[1414,17317],{},[1737,17319,17321],{"id":17320},"server-side-client-side-configuration-options","Server-Side & Client-Side Configuration Options",[58,17323,17325],{"id":17324},"client-side","Client Side",[17,17327,17328],{},"To edit the SSH client configuration:",[66,17330,17332],{"className":68,"code":17331,"language":71,"meta":15,"style":15},"nano ~/.ssh/config\n",[49,17333,17334],{"__ignoreMap":15},[75,17335,17336,17338],{"class":77,"line":78},[75,17337,17113],{"class":190},[75,17339,17340],{"class":92}," ~/.ssh/config\n",[17,17342,17343],{},[1748,17344,17345],{},"Options:",[66,17347,17350],{"className":17348,"code":17349,"language":9154},[9152],"Host RemoteServerName\n    HostName RemoteServerIp\n    Port 22                       # Port of the remote server\n    User TheUsername              # The username on the remote server\n    IdentityFile /path/to/PRIVATEKEY\n    ServerAliveInterval 120       # Keep connection alive for 120s\n",[49,17351,17349],{"__ignoreMap":15},[17,17353,17354],{},"After done, you can simply connect to the remote server like so:",[66,17356,17358],{"className":68,"code":17357,"language":71,"meta":15,"style":15},"ssh RemoteServerName\n",[49,17359,17360],{"__ignoreMap":15},[75,17361,17362,17364],{"class":77,"line":78},[75,17363,1692],{"class":190},[75,17365,17366],{"class":92}," RemoteServerName\n",[1414,17368],{},[58,17370,17372],{"id":17371},"server-side","Server Side",[17,17374,17375],{},"This is essential for security and convenience. The configuration file applies globally to all users under the remote server, but it's better to start with the following steps:",[3477,17377,17379],{"id":17378},"_1-create-a-new-user-under-the-sudo-group","1. Create a New User under the sudo Group",[66,17381,17383],{"className":68,"code":17382,"language":71,"meta":15,"style":15},"sudo adduser NewUserName\nsudo usermod -aG sudo NewUserName\nsu - NewUserName\n",[49,17384,17385,17395,17410],{"__ignoreMap":15},[75,17386,17387,17389,17392],{"class":77,"line":78},[75,17388,16210],{"class":190},[75,17390,17391],{"class":92}," adduser",[75,17393,17394],{"class":92}," NewUserName\n",[75,17396,17397,17399,17402,17405,17408],{"class":77,"line":25},[75,17398,16210],{"class":190},[75,17400,17401],{"class":92}," usermod",[75,17403,17404],{"class":171}," -aG",[75,17406,17407],{"class":92}," sudo",[75,17409,17394],{"class":92},[75,17411,17412,17415,17418],{"class":77,"line":215},[75,17413,17414],{"class":190},"su",[75,17416,17417],{"class":92}," -",[75,17419,17394],{"class":92},[3477,17421,17423],{"id":17422},"_2-check-for-the-public-key-existence-for-the-new-user","2. Check for the public key existence for the new user",[66,17425,17426],{"className":68,"code":17106,"language":71,"meta":15,"style":15},[49,17427,17428],{"__ignoreMap":15},[75,17429,17430,17432],{"class":77,"line":78},[75,17431,17113],{"class":190},[75,17433,17057],{"class":92},[17,17435,17436],{},"If none, create the directory and copy the public key from the root user:",[66,17438,17440],{"className":68,"code":17439,"language":71,"meta":15,"style":15},"sudo mkdir -p /home/NewUserName/.ssh\nsudo cp /root/.ssh/authorized_keys /home/NewUserName/.ssh/\n",[49,17441,17442,17454],{"__ignoreMap":15},[75,17443,17444,17446,17449,17451],{"class":77,"line":78},[75,17445,16210],{"class":190},[75,17447,17448],{"class":92}," mkdir",[75,17450,7371],{"class":171},[75,17452,17453],{"class":92}," /home/NewUserName/.ssh\n",[75,17455,17456,17458,17461,17464],{"class":77,"line":25},[75,17457,16210],{"class":190},[75,17459,17460],{"class":92}," cp",[75,17462,17463],{"class":92}," /root/.ssh/authorized_keys",[75,17465,17466],{"class":92}," /home/NewUserName/.ssh/\n",[17,17468,17469],{},"Set ownership and permissions:",[66,17471,17473],{"className":68,"code":17472,"language":71,"meta":15,"style":15},"sudo chown -R NewUserName:NewUserName /home/NewUserName/.ssh\nsudo chmod 700 /home/NewUserName/.ssh\nsudo chmod 600 /home/NewUserName/.ssh/authorized_keys\nchown -R NewUserName:NewUserName ~/.ssh\n",[49,17474,17475,17489,17499,17510],{"__ignoreMap":15},[75,17476,17477,17479,17481,17484,17487],{"class":77,"line":78},[75,17478,16210],{"class":190},[75,17480,16279],{"class":92},[75,17482,17483],{"class":171}," -R",[75,17485,17486],{"class":92}," NewUserName:NewUserName",[75,17488,17453],{"class":92},[75,17490,17491,17493,17495,17497],{"class":77,"line":25},[75,17492,16210],{"class":190},[75,17494,16292],{"class":92},[75,17496,17026],{"class":171},[75,17498,17453],{"class":92},[75,17500,17501,17503,17505,17507],{"class":77,"line":215},[75,17502,16210],{"class":190},[75,17504,16292],{"class":92},[75,17506,17007],{"class":171},[75,17508,17509],{"class":92}," /home/NewUserName/.ssh/authorized_keys\n",[75,17511,17512,17515,17517,17519],{"class":77,"line":221},[75,17513,17514],{"class":190},"chown",[75,17516,17483],{"class":171},[75,17518,17486],{"class":92},[75,17520,17029],{"class":92},[17,17522,17523],{},"Check again and paste manually if it doesn't exist:",[66,17525,17526],{"className":68,"code":17106,"language":71,"meta":15,"style":15},[49,17527,17528],{"__ignoreMap":15},[75,17529,17530,17532],{"class":77,"line":78},[75,17531,17113],{"class":190},[75,17533,17057],{"class":92},[3477,17535,17537],{"id":17536},"_3-server-side-ssh-daemon-configuration","3. Server-Side SSH Daemon Configuration",[17,17539,17103],{},[66,17541,17543],{"className":68,"code":17542,"language":71,"meta":15,"style":15},"sudo nano /etc/ssh/sshd_config.d/50-cloud-init.conf\n",[49,17544,17545],{"__ignoreMap":15},[75,17546,17547,17549,17552],{"class":77,"line":78},[75,17548,16210],{"class":190},[75,17550,17551],{"class":92}," nano",[75,17553,17554],{"class":92}," /etc/ssh/sshd_config.d/50-cloud-init.conf\n",[17,17556,17557,17558,1802],{},"And set ",[49,17559,17560],{},"PasswordAuthentication no",[17,17562,17563],{},"Then go to:",[66,17565,17567],{"className":68,"code":17566,"language":71,"meta":15,"style":15},"sudo nano /etc/ssh/sshd_config\n",[49,17568,17569],{"__ignoreMap":15},[75,17570,17571,17573,17575],{"class":77,"line":78},[75,17572,16210],{"class":190},[75,17574,17551],{"class":92},[75,17576,17577],{"class":92}," /etc/ssh/sshd_config\n",[17,17579,17580],{},"Uncomment and set the following:",[66,17582,17585],{"className":17583,"code":17584,"language":9154},[9152],"Port 22                  # or a different one\nListenAddress RemoteServerIp\nPermitRootLogin no       # Disable root login\nAllowUsers NewUserName@LocalHostIP\nPubkeyAuthentication yes\nPasswordAuthentication no\n",[49,17586,17584],{"__ignoreMap":15},[17,17588,17589],{},"Save and exit (Ctrl+O, Enter, Ctrl+X).",[17,17591,17592],{},"After all is set, restart and check status of the SSH service:",[66,17594,17596],{"className":68,"code":17595,"language":71,"meta":15,"style":15},"sudo systemctl restart ssh\nsudo systemctl status ssh\n",[49,17597,17598,17611],{"__ignoreMap":15},[75,17599,17600,17602,17605,17608],{"class":77,"line":78},[75,17601,16210],{"class":190},[75,17603,17604],{"class":92}," systemctl",[75,17606,17607],{"class":92}," restart",[75,17609,17610],{"class":92}," ssh\n",[75,17612,17613,17615,17617,17620],{"class":77,"line":25},[75,17614,16210],{"class":190},[75,17616,17604],{"class":92},[75,17618,17619],{"class":92}," status",[75,17621,17610],{"class":92},[17,17623,17624],{},"Try and login to the remote server:",[66,17626,17627],{"className":68,"code":17357,"language":71,"meta":15,"style":15},[49,17628,17629],{"__ignoreMap":15},[75,17630,17631,17633],{"class":77,"line":78},[75,17632,1692],{"class":190},[75,17634,17366],{"class":92},[1697,17636,17637],{},"html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}",{"title":15,"searchDepth":25,"depth":25,"links":17639},[17640,17643,17646,17647,17648,17649,17650,17655,17656],{"id":16786,"depth":25,"text":16787,"children":17641},[17642],{"id":16860,"depth":215,"text":16861},{"id":16879,"depth":25,"text":16880,"children":17644},[17645],{"id":16928,"depth":215,"text":16929},{"id":16993,"depth":25,"text":16994},{"id":17034,"depth":25,"text":17035},{"id":17062,"depth":25,"text":17063},{"id":17126,"depth":25,"text":17127},{"id":17150,"depth":25,"text":17151,"children":17651},[17652,17653,17654],{"id":17154,"depth":215,"text":17155},{"id":17174,"depth":215,"text":17175},{"id":17209,"depth":215,"text":17210},{"id":17295,"depth":25,"text":17296},{"id":17320,"depth":25,"text":17321,"children":17657},[17658,17659],{"id":17324,"depth":215,"text":17325},{"id":17371,"depth":215,"text":17372},"Generating ssh key and logging into a server","/knowledge-base/bug-banner-main2.png",{},"/knowledge-base/ssh-recipe",{"title":16781,"description":17660},{"loc":17663},"knowledge-base/ssh-recipe",[17668,7935,2386],"SSH","-c8F9_PBaqsrueFcY358fr7ZVkDK1-NTBmq2yClH-Pk",{"id":17671,"title":17672,"body":17673,"category":7926,"contentType":2374,"date":1712,"description":20711,"extension":28,"head":27,"image":20712,"meta":20713,"navigation":30,"ogImage":27,"path":20714,"readingTime":16771,"robots":27,"schemaOrg":27,"seo":20715,"sitemap":20716,"stem":20717,"tags":20718,"__hash__":20720},"knowledgeBase/knowledge-base/systemd.md","systemd Service Guide",{"type":7,"value":17674,"toc":20663},[17675,17682,17774,17804,17806,17810,17824,17833,17835,17839,17862,17876,17878,17882,17899,17907,17909,17913,17929,17937,17939,17943,17970,17990,17992,17996,18006,18010,18068,18072,18154,18158,18215,18219,18328,18332,18377,18381,18528,18532,18569,18573,18607,18611,18674,18678,18720,18722,18727,18731,18763,18767,18772,18784,18805,18810,18814,18818,18876,18883,18889,18963,18968,18985,18989,19051,19055,19113,19115,19160,19164,19251,19253,19257,19261,19293,19297,19303,19309,19352,19358,19390,19395,19427,19432,19440,19444,19449,19492,19569,19572,19616,19621,19663,19714,19719,19782,19786,19871,19873,19877,19881,19929,19935,19975,19983,20028,20032,20096,20100,20103,20169,20171,20175,20226,20231,20252,20259,20261,20265,20495,20497,20501,20506,20535,20540,20607,20612,20660],[1737,17676,17678,17679,1497],{"id":17677},"service-unit-file-etcsystemdsystemmyappservice","Service Unit File (",[49,17680,17681],{},"/etc/systemd/system/myapp.service",[66,17683,17687],{"className":17684,"code":17685,"language":17686,"meta":15,"style":15},"language-ini shiki shiki-themes github-light github-dark github-dark","[Unit]\nDescription=My App Service\nAfter=network.target\n\n[Service]\nUser=myappuser\nGroup=myappuser\nWorkingDirectory=/var/www/myapp\nExecStart=/usr/bin/node app.js\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n","ini",[49,17688,17689,17694,17701,17709,17713,17718,17726,17733,17741,17749,17757,17761,17766],{"__ignoreMap":15},[75,17690,17691],{"class":77,"line":78},[75,17692,17693],{"class":190},"[Unit]\n",[75,17695,17696,17698],{"class":77,"line":25},[75,17697,1479],{"class":81},[75,17699,17700],{"class":85},"=My App Service\n",[75,17702,17703,17706],{"class":77,"line":215},[75,17704,17705],{"class":81},"After",[75,17707,17708],{"class":85},"=network.target\n",[75,17710,17711],{"class":77,"line":221},[75,17712,527],{"emptyLinePlaceholder":30},[75,17714,17715],{"class":77,"line":235},[75,17716,17717],{"class":190},"[Service]\n",[75,17719,17720,17723],{"class":77,"line":248},[75,17721,17722],{"class":81},"User",[75,17724,17725],{"class":85},"=myappuser\n",[75,17727,17728,17731],{"class":77,"line":261},[75,17729,17730],{"class":81},"Group",[75,17732,17725],{"class":85},[75,17734,17735,17738],{"class":77,"line":274},[75,17736,17737],{"class":81},"WorkingDirectory",[75,17739,17740],{"class":85},"=/var/www/myapp\n",[75,17742,17743,17746],{"class":77,"line":615},[75,17744,17745],{"class":81},"ExecStart",[75,17747,17748],{"class":85},"=/usr/bin/node app.js\n",[75,17750,17751,17754],{"class":77,"line":636},[75,17752,17753],{"class":81},"Restart",[75,17755,17756],{"class":85},"=on-failure\n",[75,17758,17759],{"class":77,"line":642},[75,17760,527],{"emptyLinePlaceholder":30},[75,17762,17763],{"class":77,"line":663},[75,17764,17765],{"class":190},"[Install]\n",[75,17767,17768,17771],{"class":77,"line":669},[75,17769,17770],{"class":81},"WantedBy",[75,17772,17773],{"class":85},"=multi-user.target\n",[3960,17775,17776,17781],{},[17,17777,17778],{},[1748,17779,17780],{},"Explanation of Unit Sections:",[1742,17782,17783,17789,17795],{},[1745,17784,17785,17788],{},[1748,17786,17787],{},"[Unit]",": Metadata and dependencies.",[1745,17790,17791,17794],{},[1748,17792,17793],{},"[Service]",": How the service runs.",[1745,17796,17797,17800,17801,1802],{},[1748,17798,17799],{},"[Install]",": Installation details for ",[49,17802,17803],{},"enable",[1414,17805],{},[1737,17807,17809],{"id":17808},"_1-create-and-edit-the-service-file","1. Create and Edit the Service File",[66,17811,17813],{"className":68,"code":17812,"language":71,"meta":15,"style":15},"sudo nano /etc/systemd/system/myapp.service\n",[49,17814,17815],{"__ignoreMap":15},[75,17816,17817,17819,17821],{"class":77,"line":78},[75,17818,16210],{"class":190},[75,17820,17551],{"class":92},[75,17822,17823],{"class":92}," /etc/systemd/system/myapp.service\n",[1742,17825,17826],{},[1745,17827,17828,17829,17832],{},"Opens the ",[49,17830,17831],{},"myapp.service"," file in the nano editor with root privileges.",[1414,17834],{},[1737,17836,17838],{"id":17837},"_2-reload-systemd-configuration","2. Reload systemd Configuration",[66,17840,17842],{"className":68,"code":17841,"language":71,"meta":15,"style":15},"sudo systemctl daemon-reexec\nsudo systemctl daemon-reload\n",[49,17843,17844,17853],{"__ignoreMap":15},[75,17845,17846,17848,17850],{"class":77,"line":78},[75,17847,16210],{"class":190},[75,17849,17604],{"class":92},[75,17851,17852],{"class":92}," daemon-reexec\n",[75,17854,17855,17857,17859],{"class":77,"line":25},[75,17856,16210],{"class":190},[75,17858,17604],{"class":92},[75,17860,17861],{"class":92}," daemon-reload\n",[1742,17863,17864,17870],{},[1745,17865,17866,17869],{},[1748,17867,17868],{},"daemon-reexec",": Reexecute the systemd manager to pick up code changes.",[1745,17871,17872,17875],{},[1748,17873,17874],{},"daemon-reload",": Reload unit files to register new or changed services.",[1414,17877],{},[1737,17879,17881],{"id":17880},"_3-enable-the-service-at-boot","3. Enable the Service at Boot",[66,17883,17885],{"className":68,"code":17884,"language":71,"meta":15,"style":15},"sudo systemctl enable myapp.service\n",[49,17886,17887],{"__ignoreMap":15},[75,17888,17889,17891,17893,17896],{"class":77,"line":78},[75,17890,16210],{"class":190},[75,17892,17604],{"class":92},[75,17894,17895],{"class":92}," enable",[75,17897,17898],{"class":92}," myapp.service\n",[1742,17900,17901],{},[1745,17902,17903,17904,17906],{},"Creates symlinks so that ",[49,17905,17831],{}," starts automatically on system boot.",[1414,17908],{},[1737,17910,17912],{"id":17911},"_4-start-the-service","4. Start the Service",[66,17914,17916],{"className":68,"code":17915,"language":71,"meta":15,"style":15},"sudo systemctl start myapp.service\n",[49,17917,17918],{"__ignoreMap":15},[75,17919,17920,17922,17924,17927],{"class":77,"line":78},[75,17921,16210],{"class":190},[75,17923,17604],{"class":92},[75,17925,17926],{"class":92}," start",[75,17928,17898],{"class":92},[1742,17930,17931],{},[1745,17932,17933,17934,17936],{},"Launches the ",[49,17935,17831],{}," immediately.",[1414,17938],{},[1737,17940,17942],{"id":17941},"_5-check-status-and-logs","5. Check Status and Logs",[66,17944,17946],{"className":68,"code":17945,"language":71,"meta":15,"style":15},"sudo systemctl status myapp.service\njournalctl -u myapp.service -f\n",[49,17947,17948,17958],{"__ignoreMap":15},[75,17949,17950,17952,17954,17956],{"class":77,"line":78},[75,17951,16210],{"class":190},[75,17953,17604],{"class":92},[75,17955,17619],{"class":92},[75,17957,17898],{"class":92},[75,17959,17960,17963,17965,17967],{"class":77,"line":25},[75,17961,17962],{"class":190},"journalctl",[75,17964,8329],{"class":171},[75,17966,8332],{"class":92},[75,17968,17969],{"class":171}," -f\n",[1742,17971,17972,17977],{},[1745,17973,17974,17976],{},[1748,17975,10187],{},": Shows service state, PID, recent logs, and errors.",[1745,17978,17979,17982,17983,17986,17987,17989],{},[1748,17980,17981],{},"journalctl -u",": Follows logs (",[49,17984,17985],{},"-f",") for ",[49,17988,17831],{}," in real time.",[1414,17991],{},[1737,17993,17995],{"id":17994},"_6-useful-service-directives","6. Useful [Service] Directives",[17,17997,17998,17999,18001,18002,18005],{},"Below are configuration snippets you can add under the ",[49,18000,17793],{}," section to customize behavior. They are ",[1748,18003,18004],{},"not"," shell commands but unit file directives.",[58,18007,18009],{"id":18008},"_61-environment-files","6.1 Environment & Files",[66,18011,18013],{"className":17684,"code":18012,"language":17686,"meta":15,"style":15},"Environment=NODE_ENV=production PORT=8080         # Inline env vars\nEnvironmentFile=-/etc/myapp.env                   # Load env from file, ignore if missing\nWorkingDirectory=/var/www/myapp                   # Set working directory\nPIDFile=/run/myapp.pid                            # Path for PID tracking\n",[49,18014,18015,18036,18047,18057],{"__ignoreMap":15},[75,18016,18017,18020,18022,18024,18027,18030,18033],{"class":77,"line":78},[75,18018,18019],{"class":81},"Environment",[75,18021,89],{"class":85},[75,18023,10409],{"class":81},[75,18025,18026],{"class":85},"=production ",[75,18028,18029],{"class":81},"PORT",[75,18031,18032],{"class":85},"=8080         ",[75,18034,18035],{"class":2542},"# Inline env vars\n",[75,18037,18038,18041,18044],{"class":77,"line":25},[75,18039,18040],{"class":81},"EnvironmentFile",[75,18042,18043],{"class":85},"=-/etc/myapp.env                   ",[75,18045,18046],{"class":2542},"# Load env from file, ignore if missing\n",[75,18048,18049,18051,18054],{"class":77,"line":215},[75,18050,17737],{"class":81},[75,18052,18053],{"class":85},"=/var/www/myapp                   ",[75,18055,18056],{"class":2542},"# Set working directory\n",[75,18058,18059,18062,18065],{"class":77,"line":221},[75,18060,18061],{"class":81},"PIDFile",[75,18063,18064],{"class":85},"=/run/myapp.pid                            ",[75,18066,18067],{"class":2542},"# Path for PID tracking\n",[58,18069,18071],{"id":18070},"_62-resource-limits-timeouts","6.2 Resource Limits & Timeouts",[66,18073,18075],{"className":17684,"code":18074,"language":17686,"meta":15,"style":15},"TimeoutStartSec=30                 # Max seconds to start before timeout\nTimeoutStopSec=10                  # Max seconds to stop before SIGKILL\nRestartSec=5                       # Delay before restarting\nMemoryMax=200M                     # cgroup memory cap\nCPUQuota=20%                       # cgroup CPU cap\nStartLimitIntervalSec=60           # Window for restart rate limiting\nStartLimitBurst=3                  # Max restarts in interval\n",[49,18076,18077,18088,18099,18110,18121,18132,18143],{"__ignoreMap":15},[75,18078,18079,18082,18085],{"class":77,"line":78},[75,18080,18081],{"class":81},"TimeoutStartSec",[75,18083,18084],{"class":85},"=30                 ",[75,18086,18087],{"class":2542},"# Max seconds to start before timeout\n",[75,18089,18090,18093,18096],{"class":77,"line":25},[75,18091,18092],{"class":81},"TimeoutStopSec",[75,18094,18095],{"class":85},"=10                  ",[75,18097,18098],{"class":2542},"# Max seconds to stop before SIGKILL\n",[75,18100,18101,18104,18107],{"class":77,"line":215},[75,18102,18103],{"class":81},"RestartSec",[75,18105,18106],{"class":85},"=5                       ",[75,18108,18109],{"class":2542},"# Delay before restarting\n",[75,18111,18112,18115,18118],{"class":77,"line":221},[75,18113,18114],{"class":81},"MemoryMax",[75,18116,18117],{"class":85},"=200M                     ",[75,18119,18120],{"class":2542},"# cgroup memory cap\n",[75,18122,18123,18126,18129],{"class":77,"line":235},[75,18124,18125],{"class":81},"CPUQuota",[75,18127,18128],{"class":85},"=20%                       ",[75,18130,18131],{"class":2542},"# cgroup CPU cap\n",[75,18133,18134,18137,18140],{"class":77,"line":248},[75,18135,18136],{"class":81},"StartLimitIntervalSec",[75,18138,18139],{"class":85},"=60           ",[75,18141,18142],{"class":2542},"# Window for restart rate limiting\n",[75,18144,18145,18148,18151],{"class":77,"line":261},[75,18146,18147],{"class":81},"StartLimitBurst",[75,18149,18150],{"class":85},"=3                  ",[75,18152,18153],{"class":2542},"# Max restarts in interval\n",[58,18155,18157],{"id":18156},"_63-logging-output","6.3 Logging & Output",[66,18159,18161],{"className":17684,"code":18160,"language":17686,"meta":15,"style":15},"StandardOutput=journal            # Send stdout to journal\nStandardError=journal             # Send stderr to journal\nSyslogIdentifier=myapp            # Tag logs in journal\n# Or redirect to files:\nStandardOutput=append:/var/log/myapp.log\nStandardError=append:/var/log/myapp-error.log\n",[49,18162,18163,18174,18185,18196,18201,18208],{"__ignoreMap":15},[75,18164,18165,18168,18171],{"class":77,"line":78},[75,18166,18167],{"class":81},"StandardOutput",[75,18169,18170],{"class":85},"=journal            ",[75,18172,18173],{"class":2542},"# Send stdout to journal\n",[75,18175,18176,18179,18182],{"class":77,"line":25},[75,18177,18178],{"class":81},"StandardError",[75,18180,18181],{"class":85},"=journal             ",[75,18183,18184],{"class":2542},"# Send stderr to journal\n",[75,18186,18187,18190,18193],{"class":77,"line":215},[75,18188,18189],{"class":81},"SyslogIdentifier",[75,18191,18192],{"class":85},"=myapp            ",[75,18194,18195],{"class":2542},"# Tag logs in journal\n",[75,18197,18198],{"class":77,"line":221},[75,18199,18200],{"class":2542},"# Or redirect to files:\n",[75,18202,18203,18205],{"class":77,"line":235},[75,18204,18167],{"class":81},[75,18206,18207],{"class":85},"=append:/var/log/myapp.log\n",[75,18209,18210,18212],{"class":77,"line":248},[75,18211,18178],{"class":81},[75,18213,18214],{"class":85},"=append:/var/log/myapp-error.log\n",[58,18216,18218],{"id":18217},"_64-process-control-hooks","6.4 Process Control & Hooks",[66,18220,18222],{"className":17684,"code":18221,"language":17686,"meta":15,"style":15},"ExecStartPre=/usr/bin/npm install         # Run before main start\nExecStartPre=/bin/sleep 10                # Delay service start by 10s\nExecStartPost=/bin/echo \"App started\"    # Run after service has started\nExecStop=/usr/bin/node cleanup.js         # Run on service stop\nType=simple         # Run in foreground (default)\n# Alternative types:\n# Type=exec         # Run then exit\n# Type=forking      # Daemon forks to background\nKillMode=control-group                   # Kill whole process group\nKillMode=process                         # Or kill only main PID\nKillSignal=SIGINT                        # Signal to use on stop\n",[49,18223,18224,18235,18245,18259,18270,18281,18286,18291,18296,18307,18317],{"__ignoreMap":15},[75,18225,18226,18229,18232],{"class":77,"line":78},[75,18227,18228],{"class":81},"ExecStartPre",[75,18230,18231],{"class":85},"=/usr/bin/npm install         ",[75,18233,18234],{"class":2542},"# Run before main start\n",[75,18236,18237,18239,18242],{"class":77,"line":25},[75,18238,18228],{"class":81},[75,18240,18241],{"class":85},"=/bin/sleep 10                ",[75,18243,18244],{"class":2542},"# Delay service start by 10s\n",[75,18246,18247,18250,18253,18256],{"class":77,"line":215},[75,18248,18249],{"class":81},"ExecStartPost",[75,18251,18252],{"class":85},"=/bin/echo ",[75,18254,18255],{"class":92},"\"App started\"",[75,18257,18258],{"class":2542},"    # Run after service has started\n",[75,18260,18261,18264,18267],{"class":77,"line":221},[75,18262,18263],{"class":81},"ExecStop",[75,18265,18266],{"class":85},"=/usr/bin/node cleanup.js         ",[75,18268,18269],{"class":2542},"# Run on service stop\n",[75,18271,18272,18275,18278],{"class":77,"line":235},[75,18273,18274],{"class":81},"Type",[75,18276,18277],{"class":85},"=simple         ",[75,18279,18280],{"class":2542},"# Run in foreground (default)\n",[75,18282,18283],{"class":77,"line":248},[75,18284,18285],{"class":2542},"# Alternative types:\n",[75,18287,18288],{"class":77,"line":261},[75,18289,18290],{"class":2542},"# Type=exec         # Run then exit\n",[75,18292,18293],{"class":77,"line":274},[75,18294,18295],{"class":2542},"# Type=forking      # Daemon forks to background\n",[75,18297,18298,18301,18304],{"class":77,"line":615},[75,18299,18300],{"class":81},"KillMode",[75,18302,18303],{"class":85},"=control-group                   ",[75,18305,18306],{"class":2542},"# Kill whole process group\n",[75,18308,18309,18311,18314],{"class":77,"line":636},[75,18310,18300],{"class":81},[75,18312,18313],{"class":85},"=process                         ",[75,18315,18316],{"class":2542},"# Or kill only main PID\n",[75,18318,18319,18322,18325],{"class":77,"line":642},[75,18320,18321],{"class":81},"KillSignal",[75,18323,18324],{"class":85},"=SIGINT                        ",[75,18326,18327],{"class":2542},"# Signal to use on stop\n",[58,18329,18331],{"id":18330},"_65-restart-behavior","6.5 Restart Behavior",[66,18333,18335],{"className":17684,"code":18334,"language":17686,"meta":15,"style":15},"Restart=on-failure                       # Restart on non-zero exit\nRestartSec=5                             # Wait before restart\nStartLimitIntervalSec=60                 # Window for crash limit\nStartLimitBurst=3                        # Max crashes in window\n",[49,18336,18337,18347,18357,18367],{"__ignoreMap":15},[75,18338,18339,18341,18344],{"class":77,"line":78},[75,18340,17753],{"class":81},[75,18342,18343],{"class":85},"=on-failure                       ",[75,18345,18346],{"class":2542},"# Restart on non-zero exit\n",[75,18348,18349,18351,18354],{"class":77,"line":25},[75,18350,18103],{"class":81},[75,18352,18353],{"class":85},"=5                             ",[75,18355,18356],{"class":2542},"# Wait before restart\n",[75,18358,18359,18361,18364],{"class":77,"line":215},[75,18360,18136],{"class":81},[75,18362,18363],{"class":85},"=60                 ",[75,18365,18366],{"class":2542},"# Window for crash limit\n",[75,18368,18369,18371,18374],{"class":77,"line":221},[75,18370,18147],{"class":81},[75,18372,18373],{"class":85},"=3                        ",[75,18375,18376],{"class":2542},"# Max crashes in window\n",[58,18378,18380],{"id":18379},"_66-security-sandboxing","6.6 Security & Sandboxing",[66,18382,18384],{"className":17684,"code":18383,"language":17686,"meta":15,"style":15},"NoNewPrivileges=true                    # Disable privilege escalation\nPrivateTmp=true                          # Isolate /tmp and /var/tmp\nPrivateDevices=true                      # Block access to device nodes\nProtectSystem=full                       # Mount /usr, /boot, etc as read-only\nProtectHome=true                         # Block access to home directories\nProtectKernelModules=true                # Restrict kernel module loading\nProtectControlGroups=true                # Restrict cgroup access\nProtectClock=true                        # Prevent time changes\nReadOnlyPaths=/var/www/myapp             # Mount path read-only\nReadWritePaths=/var/www/myapp/uploads    # Allow writes only here\nInaccessiblePaths=/etc/ssh               # Block access to sensitive paths\nCapabilityBoundingSet=CAP_NET_BIND_SERVICE   # Limit capabilities\nAmbientCapabilities=CAP_NET_BIND_SERVICE    # Grant capability to exec\n",[49,18385,18386,18397,18408,18419,18430,18441,18452,18462,18473,18484,18495,18506,18517],{"__ignoreMap":15},[75,18387,18388,18391,18394],{"class":77,"line":78},[75,18389,18390],{"class":81},"NoNewPrivileges",[75,18392,18393],{"class":85},"=true                    ",[75,18395,18396],{"class":2542},"# Disable privilege escalation\n",[75,18398,18399,18402,18405],{"class":77,"line":25},[75,18400,18401],{"class":81},"PrivateTmp",[75,18403,18404],{"class":85},"=true                          ",[75,18406,18407],{"class":2542},"# Isolate /tmp and /var/tmp\n",[75,18409,18410,18413,18416],{"class":77,"line":215},[75,18411,18412],{"class":81},"PrivateDevices",[75,18414,18415],{"class":85},"=true                      ",[75,18417,18418],{"class":2542},"# Block access to device nodes\n",[75,18420,18421,18424,18427],{"class":77,"line":221},[75,18422,18423],{"class":81},"ProtectSystem",[75,18425,18426],{"class":85},"=full                       ",[75,18428,18429],{"class":2542},"# Mount /usr, /boot, etc as read-only\n",[75,18431,18432,18435,18438],{"class":77,"line":235},[75,18433,18434],{"class":81},"ProtectHome",[75,18436,18437],{"class":85},"=true                         ",[75,18439,18440],{"class":2542},"# Block access to home directories\n",[75,18442,18443,18446,18449],{"class":77,"line":248},[75,18444,18445],{"class":81},"ProtectKernelModules",[75,18447,18448],{"class":85},"=true                ",[75,18450,18451],{"class":2542},"# Restrict kernel module loading\n",[75,18453,18454,18457,18459],{"class":77,"line":261},[75,18455,18456],{"class":81},"ProtectControlGroups",[75,18458,18448],{"class":85},[75,18460,18461],{"class":2542},"# Restrict cgroup access\n",[75,18463,18464,18467,18470],{"class":77,"line":274},[75,18465,18466],{"class":81},"ProtectClock",[75,18468,18469],{"class":85},"=true                        ",[75,18471,18472],{"class":2542},"# Prevent time changes\n",[75,18474,18475,18478,18481],{"class":77,"line":615},[75,18476,18477],{"class":81},"ReadOnlyPaths",[75,18479,18480],{"class":85},"=/var/www/myapp             ",[75,18482,18483],{"class":2542},"# Mount path read-only\n",[75,18485,18486,18489,18492],{"class":77,"line":636},[75,18487,18488],{"class":81},"ReadWritePaths",[75,18490,18491],{"class":85},"=/var/www/myapp/uploads    ",[75,18493,18494],{"class":2542},"# Allow writes only here\n",[75,18496,18497,18500,18503],{"class":77,"line":642},[75,18498,18499],{"class":81},"InaccessiblePaths",[75,18501,18502],{"class":85},"=/etc/ssh               ",[75,18504,18505],{"class":2542},"# Block access to sensitive paths\n",[75,18507,18508,18511,18514],{"class":77,"line":663},[75,18509,18510],{"class":81},"CapabilityBoundingSet",[75,18512,18513],{"class":85},"=CAP_NET_BIND_SERVICE   ",[75,18515,18516],{"class":2542},"# Limit capabilities\n",[75,18518,18519,18522,18525],{"class":77,"line":669},[75,18520,18521],{"class":81},"AmbientCapabilities",[75,18523,18524],{"class":85},"=CAP_NET_BIND_SERVICE    ",[75,18526,18527],{"class":2542},"# Grant capability to exec\n",[58,18529,18531],{"id":18530},"_67-startupshutdown-behavior","6.7 Startup/Shutdown Behavior",[66,18533,18535],{"className":17684,"code":18534,"language":17686,"meta":15,"style":15},"After=network-online.target               # Start after network is up\nRequires=network-online.target            # Require this target\nConditionPathExists=/var/www/myapp/app.js # Only start if file exists\n",[49,18536,18537,18547,18558],{"__ignoreMap":15},[75,18538,18539,18541,18544],{"class":77,"line":78},[75,18540,17705],{"class":81},[75,18542,18543],{"class":85},"=network-online.target               ",[75,18545,18546],{"class":2542},"# Start after network is up\n",[75,18548,18549,18552,18555],{"class":77,"line":25},[75,18550,18551],{"class":81},"Requires",[75,18553,18554],{"class":85},"=network-online.target            ",[75,18556,18557],{"class":2542},"# Require this target\n",[75,18559,18560,18563,18566],{"class":77,"line":215},[75,18561,18562],{"class":81},"ConditionPathExists",[75,18564,18565],{"class":85},"=/var/www/myapp/app.js ",[75,18567,18568],{"class":2542},"# Only start if file exists\n",[58,18570,18572],{"id":18571},"_68-diagnostics-debugging","6.8 Diagnostics & Debugging",[66,18574,18576],{"className":17684,"code":18575,"language":17686,"meta":15,"style":15},"# Run in debug mode\nExecStart=/usr/bin/node --inspect app.js\n# Enable verbose debug logs\nEnvironment=DEBUG=myapp:*\n",[49,18577,18578,18583,18590,18595],{"__ignoreMap":15},[75,18579,18580],{"class":77,"line":78},[75,18581,18582],{"class":2542},"# Run in debug mode\n",[75,18584,18585,18587],{"class":77,"line":25},[75,18586,17745],{"class":81},[75,18588,18589],{"class":85},"=/usr/bin/node --inspect app.js\n",[75,18591,18592],{"class":77,"line":215},[75,18593,18594],{"class":2542},"# Enable verbose debug logs\n",[75,18596,18597,18599,18601,18604],{"class":77,"line":221},[75,18598,18019],{"class":81},[75,18600,89],{"class":85},[75,18602,18603],{"class":81},"DEBUG",[75,18605,18606],{"class":85},"=myapp:*\n",[1737,18608,18610],{"id":18609},"_7-deploying-updates-and-recommendations","7. Deploying Updates and Recommendations",[3245,18612,18613,18629,18647],{},[1745,18614,18615,18616],{},"After any change to unit or env files:",[66,18617,18619],{"className":68,"code":18618,"language":71,"meta":15,"style":15},"sudo systemctl daemon-reload\n",[49,18620,18621],{"__ignoreMap":15},[75,18622,18623,18625,18627],{"class":77,"line":78},[75,18624,16210],{"class":190},[75,18626,17604],{"class":92},[75,18628,17861],{"class":92},[1745,18630,18631,18632],{},"Restart the service:",[66,18633,18635],{"className":68,"code":18634,"language":71,"meta":15,"style":15},"sudo systemctl restart myapp.service\n",[49,18636,18637],{"__ignoreMap":15},[75,18638,18639,18641,18643,18645],{"class":77,"line":78},[75,18640,16210],{"class":190},[75,18642,17604],{"class":92},[75,18644,17607],{"class":92},[75,18646,17898],{"class":92},[1745,18648,18649,18650],{},"Check status and logs:",[66,18651,18652],{"className":68,"code":17945,"language":71,"meta":15,"style":15},[49,18653,18654,18664],{"__ignoreMap":15},[75,18655,18656,18658,18660,18662],{"class":77,"line":78},[75,18657,16210],{"class":190},[75,18659,17604],{"class":92},[75,18661,17619],{"class":92},[75,18663,17898],{"class":92},[75,18665,18666,18668,18670,18672],{"class":77,"line":25},[75,18667,17962],{"class":190},[75,18669,8329],{"class":171},[75,18671,8332],{"class":92},[75,18673,17969],{"class":171},[1737,18675,18677],{"id":18676},"_8-boot-time-analysis","8. Boot-Time Analysis",[66,18679,18681],{"className":68,"code":18680,"language":71,"meta":15,"style":15},"systemd-analyze blame               # Time taken by each service\nsystemd-analyze critical-chain      # Startup order and delays\nsystemd-analyze plot > boot.svg     # Visualize boot sequence\n",[49,18682,18683,18694,18704],{"__ignoreMap":15},[75,18684,18685,18688,18691],{"class":77,"line":78},[75,18686,18687],{"class":190},"systemd-analyze",[75,18689,18690],{"class":92}," blame",[75,18692,18693],{"class":2542},"               # Time taken by each service\n",[75,18695,18696,18698,18701],{"class":77,"line":25},[75,18697,18687],{"class":190},[75,18699,18700],{"class":92}," critical-chain",[75,18702,18703],{"class":2542},"      # Startup order and delays\n",[75,18705,18706,18708,18711,18714,18717],{"class":77,"line":215},[75,18707,18687],{"class":190},[75,18709,18710],{"class":92}," plot",[75,18712,18713],{"class":81}," >",[75,18715,18716],{"class":92}," boot.svg",[75,18718,18719],{"class":2542},"     # Visualize boot sequence\n",[1414,18721],{},[17,18723,18724],{},[1795,18725,18726],{},"Use this complete reference to cover every directive and command discussed, ensuring a robust, secure systemd service for your Node.js application.",[1737,18728,18730],{"id":18729},"_9-deploying-at-boot","9. Deploying at Boot",[66,18732,18734],{"className":68,"code":18733,"language":71,"meta":15,"style":15},"sudo systemctl enable myapp.service  # Start on boot\nsudo systemctl disable myapp.service # Disable autostart\n",[49,18735,18736,18749],{"__ignoreMap":15},[75,18737,18738,18740,18742,18744,18746],{"class":77,"line":78},[75,18739,16210],{"class":190},[75,18741,17604],{"class":92},[75,18743,17895],{"class":92},[75,18745,8332],{"class":92},[75,18747,18748],{"class":2542},"  # Start on boot\n",[75,18750,18751,18753,18755,18758,18760],{"class":77,"line":25},[75,18752,16210],{"class":190},[75,18754,17604],{"class":92},[75,18756,18757],{"class":92}," disable",[75,18759,8332],{"class":92},[75,18761,18762],{"class":2542}," # Disable autostart\n",[58,18764,18766],{"id":18765},"deploying-updates-and-recommendations","Deploying Updates and Recommendations",[3245,18768,18769],{},[1745,18770,18771],{},"After any change to the unit file or environment files, run:",[66,18773,18774],{"className":68,"code":18618,"language":71,"meta":15,"style":15},[49,18775,18776],{"__ignoreMap":15},[75,18777,18778,18780,18782],{"class":77,"line":78},[75,18779,16210],{"class":190},[75,18781,17604],{"class":92},[75,18783,17861],{"class":92},[3245,18785,18786,18802],{"start":25},[1745,18787,18631,18788],{},[66,18789,18790],{"className":68,"code":18634,"language":71,"meta":15,"style":15},[49,18791,18792],{"__ignoreMap":15},[75,18793,18794,18796,18798,18800],{"class":77,"line":78},[75,18795,16210],{"class":190},[75,18797,17604],{"class":92},[75,18799,17607],{"class":92},[75,18801,17898],{"class":92},[1745,18803,18804],{},"Verify status and logs as shown above.",[18806,18807,18809],"h1",{"id":18808},"targets-timers-and-exec-details","Targets, Timers, and Exec Details",[1737,18811,18813],{"id":18812},"_1-targets-what-they-are-and-how-to-use-them","1) Targets — What They Are and How To Use Them",[58,18815,18817],{"id":18816},"_11-what-is-a-target","1.1 What is a target?",[1742,18819,18820,18836,18855],{},[1745,18821,2415,18822,6358,18825,18828,18829,18832,18833,1802],{},[1748,18823,18824],{},"target",[49,18826,18827],{},"*.target",") is a ",[1748,18830,18831],{},"labeled checkpoint/group"," used for ordering and grouping. It ",[1748,18834,18835],{},"does not run code",[1745,18837,18838,18839,18842,18843,140,18845,18847,18848,9348,18851,18854],{},"Targets only support the ",[1748,18840,18841],{},"generic"," sections: ",[49,18844,17787],{},[49,18846,17799],{},". There is ",[1748,18849,18850],{},"no",[49,18852,18853],{},"[Target]"," section.",[1745,18856,18857,18858],{},"You use targets to:",[1742,18859,18860,18863],{},[1745,18861,18862],{},"Group services so they can start/stop together.",[1745,18864,18865,18866,6358,18869,991,18872,18875],{},"Set ",[1748,18867,18868],{},"ordering",[49,18870,18871],{},"After=",[49,18873,18874],{},"Before=",") relative to a checkpoint.",[58,18877,18879,18880],{"id":18878},"_12-options-you-actually-use-in-a-target","1.2 Options you actually use in a ",[49,18881,18882],{},".target",[17,18884,18885,18886,18888],{},"(All are generic ",[49,18887,17787],{}," options.)",[1742,18890,18891,18939,18950],{},[1745,18892,18893,18896],{},[1748,18894,18895],{},"Dependencies (pull‑in):",[1742,18897,18898,18904,18910,18924,18933],{},[1745,18899,18900,18903],{},[49,18901,18902],{},"Wants=unit1 unit2"," — start these too (weak dep).",[1745,18905,18906,18909],{},[49,18907,18908],{},"Requires=unit1"," — hard dep; if it fails/stops, this target fails/stops.",[1745,18911,18912,18915,18916,18919,18920,18923],{},[49,18913,18914],{},"BindsTo=unit1"," — like ",[49,18917,18918],{},"Requires=",", but also stops if ",[49,18921,18922],{},"unit1"," disappears (e.g., a device).",[1745,18925,18926,18929,18930,18932],{},[49,18927,18928],{},"PartOf=unit1"," — link lifecycles; stopping ",[49,18931,18922],{}," also stops this.",[1745,18934,18935,18938],{},[49,18936,18937],{},"Conflicts=unit1"," — cannot be active together.",[1745,18940,18941,9348,18944,2446,18947,1802],{},[1748,18942,18943],{},"Ordering (no pull‑in):",[49,18945,18946],{},"After=unit1",[49,18948,18949],{},"Before=unit1",[1745,18951,18952,9348,18955,18958,18959,18962],{},[1748,18953,18954],{},"Switching:",[49,18956,18957],{},"AllowIsolate=yes"," — allows ",[49,18960,18961],{},"systemctl isolate my.target"," (switch the system to only that group).",[17,18964,18965],{},[1748,18966,18967],{},"[Install] section (wiring at enable time):",[1742,18969,18970,18976],{},[1745,18971,18972,18975],{},[49,18973,18974],{},"WantedBy=multi-user.target"," — enabling your target creates a symlink so it’s part of normal boots.",[1745,18977,18978,1520,18981,18984],{},[49,18979,18980],{},"RequiredBy=…",[49,18982,18983],{},"Alias=…"," — less common; same idea.",[58,18986,18988],{"id":18987},"_13-common-targets-youll-meet","1.3 Common targets you’ll meet",[1742,18990,18991,18997,19003,19013,19027,19039],{},[1745,18992,18993,18996],{},[49,18994,18995],{},"multi-user.target"," — standard server boot (no GUI). Most daemons attach here.",[1745,18998,18999,19002],{},[49,19000,19001],{},"graphical.target"," — multi-user + display manager.",[1745,19004,19005,19008,19009,19012],{},[49,19006,19007],{},"network.target"," — networking stack is initialized (",[1748,19010,19011],{},"may not"," have an IP yet).",[1745,19014,19015,19018,19019,19022,19023,19026],{},[49,19016,19017],{},"network-online.target"," — a checkpoint that ",[1748,19020,19021],{},"waits until the network is up"," (only effective if a system-specific ",[1795,19024,19025],{},"wait-online"," unit is present; see 1.5).",[1745,19028,19029,1520,19032,1520,19035,19038],{},[49,19030,19031],{},"sockets.target",[49,19033,19034],{},"timers.target",[49,19036,19037],{},"paths.target"," — milestones socket/timer/path units hook into.",[1745,19040,19041,1520,19044,1520,19047,19050],{},[49,19042,19043],{},"shutdown.target",[49,19045,19046],{},"basic.target",[49,19048,19049],{},"rescue.target"," — other system milestones.",[58,19052,19054],{"id":19053},"_14-example-group-services-behind-a-custom-target","1.4 Example: group services behind a custom target",[66,19056,19058],{"className":17684,"code":19057,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/my-stack.target\n[Unit]\nDescription=My app stack (API + worker)\nWants=myapi.service myworker.service\nAfter=network-online.target\nAllowIsolate=yes\n\n[Install]\nWantedBy=multi-user.target\n",[49,19059,19060,19065,19069,19076,19084,19091,19099,19103,19107],{"__ignoreMap":15},[75,19061,19062],{"class":77,"line":78},[75,19063,19064],{"class":2542},"# /etc/systemd/system/my-stack.target\n",[75,19066,19067],{"class":77,"line":25},[75,19068,17693],{"class":190},[75,19070,19071,19073],{"class":77,"line":215},[75,19072,1479],{"class":81},[75,19074,19075],{"class":85},"=My app stack (API + worker)\n",[75,19077,19078,19081],{"class":77,"line":221},[75,19079,19080],{"class":81},"Wants",[75,19082,19083],{"class":85},"=myapi.service myworker.service\n",[75,19085,19086,19088],{"class":77,"line":235},[75,19087,17705],{"class":81},[75,19089,19090],{"class":85},"=network-online.target\n",[75,19092,19093,19096],{"class":77,"line":248},[75,19094,19095],{"class":81},"AllowIsolate",[75,19097,19098],{"class":85},"=yes\n",[75,19100,19101],{"class":77,"line":261},[75,19102,527],{"emptyLinePlaceholder":30},[75,19104,19105],{"class":77,"line":274},[75,19106,17765],{"class":190},[75,19108,19109,19111],{"class":77,"line":615},[75,19110,17770],{"class":81},[75,19112,17773],{"class":85},[17,19114,4427],{},[66,19116,19118],{"className":68,"code":19117,"language":71,"meta":15,"style":15},"systemctl daemon-reload\nsystemctl enable --now my-stack.target\nsystemctl start my-stack.target         # starts the group\nsystemctl list-dependencies my-stack.target\n",[49,19119,19120,19127,19139,19151],{"__ignoreMap":15},[75,19121,19122,19125],{"class":77,"line":78},[75,19123,19124],{"class":190},"systemctl",[75,19126,17861],{"class":92},[75,19128,19129,19131,19133,19136],{"class":77,"line":25},[75,19130,19124],{"class":190},[75,19132,17895],{"class":92},[75,19134,19135],{"class":171}," --now",[75,19137,19138],{"class":92}," my-stack.target\n",[75,19140,19141,19143,19145,19148],{"class":77,"line":215},[75,19142,19124],{"class":190},[75,19144,17926],{"class":92},[75,19146,19147],{"class":92}," my-stack.target",[75,19149,19150],{"class":2542},"         # starts the group\n",[75,19152,19153,19155,19158],{"class":77,"line":221},[75,19154,19124],{"class":190},[75,19156,19157],{"class":92}," list-dependencies",[75,19159,19138],{"class":92},[58,19161,19163],{"id":19162},"_15-afternetwork-onlinetarget-ordering-vs-pulling-it-in","1.5 \"After=network-online.target\" (ordering) vs pulling it in",[1742,19165,19166,19176,19200,19240],{},[1745,19167,19168,19171,19172,19175],{},[49,19169,19170],{},"After=network-online.target"," ⇒ start ",[1748,19173,19174],{},"after"," that checkpoint (ordering only).",[1745,19177,19178,19179,19182,19183],{},"If you also want it ",[1748,19180,19181],{},"included"," in the boot transaction, add:",[66,19184,19186],{"className":17684,"code":19185,"language":17686,"meta":15,"style":15},"Wants=network-online.target\nAfter=network-online.target\n",[49,19187,19188,19194],{"__ignoreMap":15},[75,19189,19190,19192],{"class":77,"line":78},[75,19191,19080],{"class":81},[75,19193,19090],{"class":85},[75,19195,19196,19198],{"class":77,"line":25},[75,19197,17705],{"class":81},[75,19199,19090],{"class":85},[1745,19201,19202,19203],{},"Ensure your network stack provides the wait helper:",[66,19204,19206],{"className":68,"code":19205,"language":71,"meta":15,"style":15},"# for systemd-networkd setups\nsystemctl status systemd-networkd-wait-online.service\n\n# for NetworkManager setups\nsystemctl status NetworkManager-wait-online.service\n",[49,19207,19208,19213,19222,19226,19231],{"__ignoreMap":15},[75,19209,19210],{"class":77,"line":78},[75,19211,19212],{"class":2542},"# for systemd-networkd setups\n",[75,19214,19215,19217,19219],{"class":77,"line":25},[75,19216,19124],{"class":190},[75,19218,17619],{"class":92},[75,19220,19221],{"class":92}," systemd-networkd-wait-online.service\n",[75,19223,19224],{"class":77,"line":215},[75,19225,527],{"emptyLinePlaceholder":30},[75,19227,19228],{"class":77,"line":221},[75,19229,19230],{"class":2542},"# for NetworkManager setups\n",[75,19232,19233,19235,19237],{"class":77,"line":235},[75,19234,19124],{"class":190},[75,19236,17619],{"class":92},[75,19238,19239],{"class":92}," NetworkManager-wait-online.service\n",[1745,19241,19242,19243,19246,19247,19250],{},"Remember: ",[1795,19244,19245],{},"online"," is a ",[1748,19248,19249],{},"boot-time checkpoint",", not a live network monitor.",[1414,19252],{},[1737,19254,19256],{"id":19255},"_2-timers-cron-replacement-clear-mechanics","2) Timers — Cron Replacement (Clear Mechanics)",[58,19258,19260],{"id":19259},"_21-how-a-timer-triggers-a-service","2.1 How a timer triggers a service",[1742,19262,19263,19277,19287],{},[1745,19264,19265,19266,19269,19270,9348,19273,19276],{},"Naming rule: ",[49,19267,19268],{},"X.timer"," triggers ",[49,19271,19272],{},"X.service",[1748,19274,19275],{},"by default"," (basename match).",[1745,19278,19279,19280,19283,19284,18854],{},"To trigger a differently named unit, set ",[49,19281,19282],{},"Unit=that.service"," in the timer’s ",[49,19285,19286],{},"[Timer]",[1745,19288,19289,19292],{},[1748,19290,19291],{},"Enable the timer",", not the service, to put it on schedule.",[58,19294,19296],{"id":19295},"_22-timer-sections-and-key-options","2.2 Timer sections and key options",[17,19298,19299,19300,19302],{},"Timer units have a ",[49,19301,19286],{}," section with two families of triggers:",[17,19304,19305,19308],{},[1748,19306,19307],{},"A) Monotonic (relative) triggers"," — based on elapsed time",[1742,19310,19311,19320,19326,19336,19346],{},[1745,19312,19313,19316,19317,1802],{},[49,19314,19315],{},"OnBootSec=5min"," — 5 minutes ",[1748,19318,19319],{},"after boot",[1745,19321,19322,19325],{},[49,19323,19324],{},"OnStartupSec=30s"," — 30s after the service manager starts (subtle difference from boot).",[1745,19327,19328,19331,19332,19335],{},[49,19329,19330],{},"OnActiveSec=1h"," — 1 hour after the ",[1748,19333,19334],{},"timer"," became active.",[1745,19337,19338,19341,19342,19345],{},[49,19339,19340],{},"OnUnitActiveSec=15min"," — 15 minutes after the ",[1748,19343,19344],{},"triggered unit"," last ran.",[1745,19347,19348,19351],{},[49,19349,19350],{},"OnUnitInactiveSec=10min"," — 10 minutes after the unit last went inactive.",[17,19353,19354,19357],{},[1748,19355,19356],{},"B) Calendar (wall‑clock) triggers"," — real dates/times",[1742,19359,19360,19366,19372,19378,19384],{},[1745,19361,19362,19365],{},[49,19363,19364],{},"OnCalendar=daily"," — every day at 00:00.",[1745,19367,19368,19371],{},[49,19369,19370],{},"OnCalendar=02:00"," — every day at 02:00.",[1745,19373,19374,19377],{},[49,19375,19376],{},"OnCalendar=Mon..Fri 02:00"," — weekdays at 02:00.",[1745,19379,19380,19383],{},[49,19381,19382],{},"OnCalendar=*-*-01 00:00"," — first day of every month at midnight.",[1745,19385,19386,19389],{},[49,19387,19388],{},"OnCalendar=2025-08-12 14:30:00"," — specific date/time.",[17,19391,19392],{},[1748,19393,19394],{},"Behavior modifiers",[1742,19396,19397,19403,19409,19415,19421],{},[1745,19398,19399,19402],{},[49,19400,19401],{},"Persistent=true"," — if a run was missed (machine was off), run once on the next boot.",[1745,19404,19405,19408],{},[49,19406,19407],{},"RandomizedDelaySec=5m"," — add a random delay to spread load.",[1745,19410,19411,19414],{},[49,19412,19413],{},"AccuracySec=1m"," — coalesce timers for efficiency; use smaller if you need precision.",[1745,19416,19417,19420],{},[49,19418,19419],{},"WakeSystem=yes"," — attempt to wake from suspend for the job (hardware/permissions permitting).",[1745,19422,19423,19426],{},[49,19424,19425],{},"Unit=foo.service"," — (optional) explicit unit name to trigger.",[17,19428,19429],{},[1748,19430,19431],{},"[Install] for timers",[1742,19433,19434],{},[1745,19435,19436,19439],{},[49,19437,19438],{},"WantedBy=timers.target"," — enabling the timer hooks it into the system’s timer milestone.",[58,19441,19443],{"id":19442},"_23-minimal-working-pairs-copypaste","2.3 Minimal working pairs (copy/paste)",[17,19445,19446],{},[1748,19447,19448],{},"Nightly backup at 02:00, catch up if missed",[66,19450,19452],{"className":17684,"code":19451,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/backup.service\n[Unit]\nDescription=Nightly backup job\n\n[Service]\nType=oneshot\nExecStart=/usr/local/bin/backup.sh\n",[49,19453,19454,19459,19463,19470,19474,19478,19485],{"__ignoreMap":15},[75,19455,19456],{"class":77,"line":78},[75,19457,19458],{"class":2542},"# /etc/systemd/system/backup.service\n",[75,19460,19461],{"class":77,"line":25},[75,19462,17693],{"class":190},[75,19464,19465,19467],{"class":77,"line":215},[75,19466,1479],{"class":81},[75,19468,19469],{"class":85},"=Nightly backup job\n",[75,19471,19472],{"class":77,"line":221},[75,19473,527],{"emptyLinePlaceholder":30},[75,19475,19476],{"class":77,"line":235},[75,19477,17717],{"class":190},[75,19479,19480,19482],{"class":77,"line":248},[75,19481,18274],{"class":81},[75,19483,19484],{"class":85},"=oneshot\n",[75,19486,19487,19489],{"class":77,"line":261},[75,19488,17745],{"class":81},[75,19490,19491],{"class":85},"=/usr/local/bin/backup.sh\n",[66,19493,19495],{"className":17684,"code":19494,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/backup.timer\n[Unit]\nDescription=Run backup every night at 02:00\n\n[Timer]\nOnCalendar=02:00\nPersistent=true\nRandomizedDelaySec=5m\nAccuracySec=1m\n\n[Install]\nWantedBy=timers.target\n",[49,19496,19497,19502,19506,19513,19517,19522,19530,19538,19546,19554,19558,19562],{"__ignoreMap":15},[75,19498,19499],{"class":77,"line":78},[75,19500,19501],{"class":2542},"# /etc/systemd/system/backup.timer\n",[75,19503,19504],{"class":77,"line":25},[75,19505,17693],{"class":190},[75,19507,19508,19510],{"class":77,"line":215},[75,19509,1479],{"class":81},[75,19511,19512],{"class":85},"=Run backup every night at 02:00\n",[75,19514,19515],{"class":77,"line":221},[75,19516,527],{"emptyLinePlaceholder":30},[75,19518,19519],{"class":77,"line":235},[75,19520,19521],{"class":190},"[Timer]\n",[75,19523,19524,19527],{"class":77,"line":248},[75,19525,19526],{"class":81},"OnCalendar",[75,19528,19529],{"class":85},"=02:00\n",[75,19531,19532,19535],{"class":77,"line":261},[75,19533,19534],{"class":81},"Persistent",[75,19536,19537],{"class":85},"=true\n",[75,19539,19540,19543],{"class":77,"line":274},[75,19541,19542],{"class":81},"RandomizedDelaySec",[75,19544,19545],{"class":85},"=5m\n",[75,19547,19548,19551],{"class":77,"line":615},[75,19549,19550],{"class":81},"AccuracySec",[75,19552,19553],{"class":85},"=1m\n",[75,19555,19556],{"class":77,"line":636},[75,19557,527],{"emptyLinePlaceholder":30},[75,19559,19560],{"class":77,"line":642},[75,19561,17765],{"class":190},[75,19563,19564,19566],{"class":77,"line":663},[75,19565,17770],{"class":81},[75,19567,19568],{"class":85},"=timers.target\n",[17,19570,19571],{},"Commands:",[66,19573,19575],{"className":68,"code":19574,"language":71,"meta":15,"style":15},"systemctl daemon-reload\nsystemctl enable --now backup.timer\nsystemctl list-timers --all\njournalctl -u backup.service -e\n",[49,19576,19577,19583,19594,19604],{"__ignoreMap":15},[75,19578,19579,19581],{"class":77,"line":78},[75,19580,19124],{"class":190},[75,19582,17861],{"class":92},[75,19584,19585,19587,19589,19591],{"class":77,"line":25},[75,19586,19124],{"class":190},[75,19588,17895],{"class":92},[75,19590,19135],{"class":171},[75,19592,19593],{"class":92}," backup.timer\n",[75,19595,19596,19598,19601],{"class":77,"line":215},[75,19597,19124],{"class":190},[75,19599,19600],{"class":92}," list-timers",[75,19602,19603],{"class":171}," --all\n",[75,19605,19606,19608,19610,19613],{"class":77,"line":221},[75,19607,17962],{"class":190},[75,19609,8329],{"class":171},[75,19611,19612],{"class":92}," backup.service",[75,19614,19615],{"class":171}," -e\n",[17,19617,19618],{},[1748,19619,19620],{},"Every 15 minutes since last run finished",[66,19622,19624],{"className":17684,"code":19623,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/clean-cache.service\n[Unit]\nDescription=Clean app cache\n\n[Service]\nType=oneshot\nExecStart=/usr/local/bin/clean_cache.sh\n",[49,19625,19626,19631,19635,19642,19646,19650,19656],{"__ignoreMap":15},[75,19627,19628],{"class":77,"line":78},[75,19629,19630],{"class":2542},"# /etc/systemd/system/clean-cache.service\n",[75,19632,19633],{"class":77,"line":25},[75,19634,17693],{"class":190},[75,19636,19637,19639],{"class":77,"line":215},[75,19638,1479],{"class":81},[75,19640,19641],{"class":85},"=Clean app cache\n",[75,19643,19644],{"class":77,"line":221},[75,19645,527],{"emptyLinePlaceholder":30},[75,19647,19648],{"class":77,"line":235},[75,19649,17717],{"class":190},[75,19651,19652,19654],{"class":77,"line":248},[75,19653,18274],{"class":81},[75,19655,19484],{"class":85},[75,19657,19658,19660],{"class":77,"line":261},[75,19659,17745],{"class":81},[75,19661,19662],{"class":85},"=/usr/local/bin/clean_cache.sh\n",[66,19664,19666],{"className":17684,"code":19665,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/clean-cache.timer\n[Unit]\nDescription=Clean cache every 15 minutes (interval since last run)\n\n[Timer]\nOnUnitActiveSec=15min\n\n[Install]\nWantedBy=timers.target\n",[49,19667,19668,19673,19677,19684,19688,19692,19700,19704,19708],{"__ignoreMap":15},[75,19669,19670],{"class":77,"line":78},[75,19671,19672],{"class":2542},"# /etc/systemd/system/clean-cache.timer\n",[75,19674,19675],{"class":77,"line":25},[75,19676,17693],{"class":190},[75,19678,19679,19681],{"class":77,"line":215},[75,19680,1479],{"class":81},[75,19682,19683],{"class":85},"=Clean cache every 15 minutes (interval since last run)\n",[75,19685,19686],{"class":77,"line":221},[75,19687,527],{"emptyLinePlaceholder":30},[75,19689,19690],{"class":77,"line":235},[75,19691,19521],{"class":190},[75,19693,19694,19697],{"class":77,"line":248},[75,19695,19696],{"class":81},"OnUnitActiveSec",[75,19698,19699],{"class":85},"=15min\n",[75,19701,19702],{"class":77,"line":261},[75,19703,527],{"emptyLinePlaceholder":30},[75,19705,19706],{"class":77,"line":274},[75,19707,17765],{"class":190},[75,19709,19710,19712],{"class":77,"line":615},[75,19711,17770],{"class":81},[75,19713,19568],{"class":85},[17,19715,19716],{},[1748,19717,19718],{},"Hourly with jitter (avoid thundering herd)",[66,19720,19722],{"className":17684,"code":19721,"language":17686,"meta":15,"style":15},"# /etc/systemd/system/report.timer\n[Unit]\nDescription=Generate report hourly with jitter\n\n[Timer]\nOnCalendar=hourly\nRandomizedDelaySec=10m\nAccuracySec=1m\n\n[Install]\nWantedBy=timers.target\n",[49,19723,19724,19729,19733,19740,19744,19748,19755,19762,19768,19772,19776],{"__ignoreMap":15},[75,19725,19726],{"class":77,"line":78},[75,19727,19728],{"class":2542},"# /etc/systemd/system/report.timer\n",[75,19730,19731],{"class":77,"line":25},[75,19732,17693],{"class":190},[75,19734,19735,19737],{"class":77,"line":215},[75,19736,1479],{"class":81},[75,19738,19739],{"class":85},"=Generate report hourly with jitter\n",[75,19741,19742],{"class":77,"line":221},[75,19743,527],{"emptyLinePlaceholder":30},[75,19745,19746],{"class":77,"line":235},[75,19747,19521],{"class":190},[75,19749,19750,19752],{"class":77,"line":248},[75,19751,19526],{"class":81},[75,19753,19754],{"class":85},"=hourly\n",[75,19756,19757,19759],{"class":77,"line":261},[75,19758,19542],{"class":81},[75,19760,19761],{"class":85},"=10m\n",[75,19763,19764,19766],{"class":77,"line":274},[75,19765,19550],{"class":81},[75,19767,19553],{"class":85},[75,19769,19770],{"class":77,"line":615},[75,19771,527],{"emptyLinePlaceholder":30},[75,19773,19774],{"class":77,"line":636},[75,19775,17765],{"class":190},[75,19777,19778,19780],{"class":77,"line":642},[75,19779,17770],{"class":81},[75,19781,19568],{"class":85},[58,19783,19785],{"id":19784},"_24-useful-timer-diagnostics","2.4 Useful timer diagnostics",[66,19787,19789],{"className":68,"code":19788,"language":71,"meta":15,"style":15},"systemctl list-timers --all                 # see next/last runs\nsystemctl status myjob.timer                # status of the schedule\nsystemctl status myjob.service              # status of the job itself\njournalctl -u myjob.service -e              # logs from the job\nsystemd-analyze calendar 'Mon..Fri 02:00'   # parse/preview a calendar spec\nsystemd-run --on-active=5s /bin/echo test   # ad-hoc timer-run to test mechanics\n",[49,19790,19791,19803,19815,19827,19841,19854],{"__ignoreMap":15},[75,19792,19793,19795,19797,19800],{"class":77,"line":78},[75,19794,19124],{"class":190},[75,19796,19600],{"class":92},[75,19798,19799],{"class":171}," --all",[75,19801,19802],{"class":2542},"                 # see next/last runs\n",[75,19804,19805,19807,19809,19812],{"class":77,"line":25},[75,19806,19124],{"class":190},[75,19808,17619],{"class":92},[75,19810,19811],{"class":92}," myjob.timer",[75,19813,19814],{"class":2542},"                # status of the schedule\n",[75,19816,19817,19819,19821,19824],{"class":77,"line":215},[75,19818,19124],{"class":190},[75,19820,17619],{"class":92},[75,19822,19823],{"class":92}," myjob.service",[75,19825,19826],{"class":2542},"              # status of the job itself\n",[75,19828,19829,19831,19833,19835,19838],{"class":77,"line":221},[75,19830,17962],{"class":190},[75,19832,8329],{"class":171},[75,19834,19823],{"class":92},[75,19836,19837],{"class":171}," -e",[75,19839,19840],{"class":2542},"              # logs from the job\n",[75,19842,19843,19845,19848,19851],{"class":77,"line":235},[75,19844,18687],{"class":190},[75,19846,19847],{"class":92}," calendar",[75,19849,19850],{"class":92}," 'Mon..Fri 02:00'",[75,19852,19853],{"class":2542},"   # parse/preview a calendar spec\n",[75,19855,19856,19859,19862,19865,19868],{"class":77,"line":248},[75,19857,19858],{"class":190},"systemd-run",[75,19860,19861],{"class":171}," --on-active=5s",[75,19863,19864],{"class":92}," /bin/echo",[75,19866,19867],{"class":92}," test",[75,19869,19870],{"class":2542},"   # ad-hoc timer-run to test mechanics\n",[1414,19872],{},[1737,19874,19876],{"id":19875},"_3-services-exec-types-restart-and-sandboxing-quick-but-precise","3) Services — Exec, Types, Restart, and Sandboxing (Quick but Precise)",[58,19878,19880],{"id":19879},"_31-exec-lines-startstopreload-hooks","3.1 Exec lines (start/stop/reload hooks)",[1742,19882,19883,19898,19908],{},[1745,19884,19885,19888,19889,1751,19892,19895,19896,2030],{},[1748,19886,19887],{},"Absolute path required"," for the first token in ",[49,19890,19891],{},"ExecStart=",[49,19893,19894],{},"/usr/bin/node",", not ",[49,19897,1560],{},[1745,19899,19900,19901,19904,19905,1802],{},"If you need shell features (pipes, ",[49,19902,19903],{},"&&","), wrap them: ",[49,19906,19907],{},"ExecStart=/bin/bash -lc 'cmd1 && cmd2'",[1745,19909,19910,19911,1520,19914,1520,19917,1520,19920,1520,19923,1520,19926,1802],{},"Hooks: ",[49,19912,19913],{},"ExecStartPre=…",[49,19915,19916],{},"ExecStart=…",[49,19918,19919],{},"ExecStartPost=…",[49,19921,19922],{},"ExecReload=…",[49,19924,19925],{},"ExecStop=…",[49,19927,19928],{},"ExecStopPost=…",[17,19930,19931,19934],{},[1748,19932,19933],{},"Exec prefixes"," (put before the path on that line):",[1742,19936,19937,19942,19954,19965],{},[1745,19938,19939,19941],{},[49,19940,11526],{},"  → ignore non‑zero exit (best‑effort step).",[1745,19943,19944,19946,19947,19950,19951,2030],{},[49,19945,6612],{},"  → run with full root privileges (bypass ",[49,19948,19949],{},"User=","/caps ",[1748,19952,19953],{},"for this command only",[1745,19955,19956,19958,19959,991,19961,19964],{},[49,19957,6361],{},"  → elevate only user/group creds (ignore ",[49,19960,19949],{},[49,19962,19963],{},"Group=",") but keep other sandboxes.",[1745,19966,19967,19970,19971,19974],{},[49,19968,19969],{},"@","  → set custom ",[49,19972,19973],{},"argv[0]"," (rare).",[58,19976,19978,19979,19982],{"id":19977},"_32-type-how-systemd-decides-the-service-is-ready","3.2 ",[49,19980,19981],{},"Type="," (how systemd decides the service is ready)",[1742,19984,19985,19994,20003,20012,20022],{},[1745,19986,19987,19990,19991,19993],{},[49,19988,19989],{},"Type=simple"," (default) — run ",[49,19992,17745],{}," and consider it the main process.",[1745,19995,19996,19999,20000,20002],{},[49,19997,19998],{},"Type=oneshot"," — short tasks; can have multiple ",[49,20001,19891],{}," lines.",[1745,20004,20005,20008,20009,1802],{},[49,20006,20007],{},"Type=forking"," — legacy daemons that daemonize themselves; usually add ",[49,20010,20011],{},"PIDFile=",[1745,20013,20014,20017,20018,20021],{},[49,20015,20016],{},"Type=notify"," — service calls ",[49,20019,20020],{},"sd_notify(READY=1)"," when ready.",[1745,20023,20024,20027],{},[49,20025,20026],{},"Type=dbus"," — becomes ready when it acquires a D‑Bus name.",[58,20029,20031],{"id":20030},"_33-identity-environment-and-restart","3.3 Identity, environment, and restart",[1742,20033,20034,20041,20047,20056,20070,20086],{},[1745,20035,20036,2446,20038,20040],{},[49,20037,19949],{},[49,20039,19963],{}," — drop privileges.",[1745,20042,20043,20046],{},[49,20044,20045],{},"WorkingDirectory="," — set CWD.",[1745,20048,20049,20052,20053,1802],{},[49,20050,20051],{},"Environment=KEY=VAL …"," and/or ",[49,20054,20055],{},"EnvironmentFile=/etc/myapp.env",[1745,20057,20058,20059,20062,20063,20066,20067,1802],{},"Restart policy: ",[49,20060,20061],{},"Restart=on-failure"," (good default), ",[49,20064,20065],{},"RestartSec=1s",". Others: ",[49,20068,20069],{},"no|on-success|on-abnormal|on-abort|always",[1745,20071,20072,20073,20076,20077,9942,20080,1520,20083,1802],{},"I/O and timeouts: ",[49,20074,20075],{},"StandardOutput=journal"," (default), ",[49,20078,20079],{},"StandardError=inherit|journal|null|file:/path",[49,20081,20082],{},"TimeoutStartSec=",[49,20084,20085],{},"TimeoutStopSec=",[1745,20087,20088,20089,20092,20093,1802],{},"Kill behavior: ",[49,20090,20091],{},"KillMode=control-group"," (default: kill cgroup), ",[49,20094,20095],{},"SendSIGKILL=",[58,20097,20099],{"id":20098},"_34-minimal-hardening-flags-dial-back-if-needed","3.4 Minimal hardening flags (dial back if needed)",[17,20101,20102],{},"Add cautiously; remove if your app needs broader access.",[1742,20104,20105,20110,20115,20123,20128,20139,20145,20153,20163],{},[1745,20106,20107],{},[49,20108,20109],{},"NoNewPrivileges=yes",[1745,20111,20112],{},[49,20113,20114],{},"PrivateTmp=yes",[1745,20116,20117,3497,20120,1497],{},[49,20118,20119],{},"ProtectSystem=strict",[49,20121,20122],{},"full",[1745,20124,20125],{},[49,20126,20127],{},"ProtectHome=read-only|yes",[1745,20129,20130,1520,20133,1520,20136],{},[49,20131,20132],{},"ProtectKernelTunables=yes",[49,20134,20135],{},"ProtectKernelLogs=yes",[49,20137,20138],{},"ProtectControlGroups=yes",[1745,20140,20141,20144],{},[49,20142,20143],{},"RestrictAddressFamilies=AF_INET AF_INET6"," (plus any others you need)",[1745,20146,20147,1520,20150],{},[49,20148,20149],{},"RestrictSUIDSGID=yes",[49,20151,20152],{},"LockPersonality=yes",[1745,20154,20155,20158,20159,20162],{},[49,20156,20157],{},"CapabilityBoundingSet="," (drop all or whitelist), ",[49,20160,20161],{},"AmbientCapabilities="," (grant minimal caps)",[1745,20164,20165,20168],{},[49,20166,20167],{},"SystemCallFilter=@system-service"," (start here, narrow if you can)",[1414,20170],{},[1737,20172,20174],{"id":20173},"_4-ordering-vs-dependencies-one-glance-cheat-sheet","4) Ordering vs Dependencies — One-Glance Cheat Sheet",[1742,20176,20177,20192,20205,20218],{},[1745,20178,20179,9348,20182,2446,20185,20188,20189,20191],{},[1748,20180,20181],{},"Ordering only:",[49,20183,20184],{},"After=X",[49,20186,20187],{},"Before=X"," (does ",[1748,20190,18004],{}," pull X in).",[1745,20193,20194,9348,20197,20200,20201,20204],{},[1748,20195,20196],{},"Pull‑in:",[49,20198,20199],{},"Wants=X"," (weak) / ",[49,20202,20203],{},"Requires=X"," (strong; if X fails/stops, so do you).",[1745,20206,20207,9348,20210,20213,20214,20217],{},[1748,20208,20209],{},"Lifecycle linking:",[49,20211,20212],{},"PartOf=X"," (stop/restart together), ",[49,20215,20216],{},"BindsTo=X"," (like Requires + stop if X disappears).",[1745,20219,20220,9348,20223,1802],{},[1748,20221,20222],{},"Mutual exclusion:",[49,20224,20225],{},"Conflicts=X",[17,20227,20228],{},[1748,20229,20230],{},"Common pattern — service that needs network at start:",[66,20232,20234],{"className":17684,"code":20233,"language":17686,"meta":15,"style":15},"[Unit]\nWants=network-online.target\nAfter=network-online.target\n",[49,20235,20236,20240,20246],{"__ignoreMap":15},[75,20237,20238],{"class":77,"line":78},[75,20239,17693],{"class":190},[75,20241,20242,20244],{"class":77,"line":25},[75,20243,19080],{"class":81},[75,20245,19090],{"class":85},[75,20247,20248,20250],{"class":77,"line":215},[75,20249,17705],{"class":81},[75,20251,19090],{"class":85},[17,20253,20254,20255,20258],{},"…and ensure your system has a ",[49,20256,20257],{},"*-wait-online.service"," for your network stack.",[1414,20260],{},[1737,20262,20264],{"id":20263},"_5-useful-commands-diagnostics-copypaste","5) Useful Commands & Diagnostics (Copy/Paste)",[66,20266,20268],{"className":68,"code":20267,"language":71,"meta":15,"style":15},"# See active units by type\nsystemctl list-units --type=service --state=running\nsystemctl list-units --type=target\n\n# Inspect unit dependencies/ordering\nsystemctl list-dependencies my-stack.target\nsystemd-analyze critical-chain myapi.service\n\n# Show the effective unit file (including drop-ins)\nsystemctl cat myapi.service\n\n# Verify a unit file’s syntax\nsystemd-analyze verify /etc/systemd/system/myapi.service\n\n# Enable/disable/start/stop/show state\nsystemctl enable --now foo.service\nsystemctl disable --now foo.service\nsystemctl is-enabled foo.service\nsystemctl is-active foo.service\n\n# Logs\njournalctl -u foo.service -b -e -f\n\n# Timers overview and testing\nsystemctl list-timers --all\nsystemd-analyze calendar 'Mon..Fri 02:00'\nsystemd-run --on-active=10s /usr/bin/echo test\n\n# Wait-online helpers (pick whichever your system uses)\nsystemctl status systemd-networkd-wait-online.service\nsystemctl status NetworkManager-wait-online.service\n",[49,20269,20270,20275,20288,20297,20301,20306,20314,20323,20327,20332,20341,20345,20350,20359,20363,20368,20379,20389,20398,20407,20411,20416,20431,20435,20440,20448,20457,20470,20474,20479,20487],{"__ignoreMap":15},[75,20271,20272],{"class":77,"line":78},[75,20273,20274],{"class":2542},"# See active units by type\n",[75,20276,20277,20279,20282,20285],{"class":77,"line":25},[75,20278,19124],{"class":190},[75,20280,20281],{"class":92}," list-units",[75,20283,20284],{"class":171}," --type=service",[75,20286,20287],{"class":171}," --state=running\n",[75,20289,20290,20292,20294],{"class":77,"line":215},[75,20291,19124],{"class":190},[75,20293,20281],{"class":92},[75,20295,20296],{"class":171}," --type=target\n",[75,20298,20299],{"class":77,"line":221},[75,20300,527],{"emptyLinePlaceholder":30},[75,20302,20303],{"class":77,"line":235},[75,20304,20305],{"class":2542},"# Inspect unit dependencies/ordering\n",[75,20307,20308,20310,20312],{"class":77,"line":248},[75,20309,19124],{"class":190},[75,20311,19157],{"class":92},[75,20313,19138],{"class":92},[75,20315,20316,20318,20320],{"class":77,"line":261},[75,20317,18687],{"class":190},[75,20319,18700],{"class":92},[75,20321,20322],{"class":92}," myapi.service\n",[75,20324,20325],{"class":77,"line":274},[75,20326,527],{"emptyLinePlaceholder":30},[75,20328,20329],{"class":77,"line":615},[75,20330,20331],{"class":2542},"# Show the effective unit file (including drop-ins)\n",[75,20333,20334,20336,20339],{"class":77,"line":636},[75,20335,19124],{"class":190},[75,20337,20338],{"class":92}," cat",[75,20340,20322],{"class":92},[75,20342,20343],{"class":77,"line":642},[75,20344,527],{"emptyLinePlaceholder":30},[75,20346,20347],{"class":77,"line":663},[75,20348,20349],{"class":2542},"# Verify a unit file’s syntax\n",[75,20351,20352,20354,20356],{"class":77,"line":669},[75,20353,18687],{"class":190},[75,20355,4015],{"class":92},[75,20357,20358],{"class":92}," /etc/systemd/system/myapi.service\n",[75,20360,20361],{"class":77,"line":675},[75,20362,527],{"emptyLinePlaceholder":30},[75,20364,20365],{"class":77,"line":684},[75,20366,20367],{"class":2542},"# Enable/disable/start/stop/show state\n",[75,20369,20370,20372,20374,20376],{"class":77,"line":690},[75,20371,19124],{"class":190},[75,20373,17895],{"class":92},[75,20375,19135],{"class":171},[75,20377,20378],{"class":92}," foo.service\n",[75,20380,20381,20383,20385,20387],{"class":77,"line":2254},[75,20382,19124],{"class":190},[75,20384,18757],{"class":92},[75,20386,19135],{"class":171},[75,20388,20378],{"class":92},[75,20390,20391,20393,20396],{"class":77,"line":2259},[75,20392,19124],{"class":190},[75,20394,20395],{"class":92}," is-enabled",[75,20397,20378],{"class":92},[75,20399,20400,20402,20405],{"class":77,"line":2276},[75,20401,19124],{"class":190},[75,20403,20404],{"class":92}," is-active",[75,20406,20378],{"class":92},[75,20408,20409],{"class":77,"line":2294},[75,20410,527],{"emptyLinePlaceholder":30},[75,20412,20413],{"class":77,"line":2299},[75,20414,20415],{"class":2542},"# Logs\n",[75,20417,20418,20420,20422,20425,20427,20429],{"class":77,"line":2308},[75,20419,17962],{"class":190},[75,20421,8329],{"class":171},[75,20423,20424],{"class":92}," foo.service",[75,20426,194],{"class":171},[75,20428,19837],{"class":171},[75,20430,17969],{"class":171},[75,20432,20433],{"class":77,"line":2792},[75,20434,527],{"emptyLinePlaceholder":30},[75,20436,20437],{"class":77,"line":2802},[75,20438,20439],{"class":2542},"# Timers overview and testing\n",[75,20441,20442,20444,20446],{"class":77,"line":2810},[75,20443,19124],{"class":190},[75,20445,19600],{"class":92},[75,20447,19603],{"class":171},[75,20449,20450,20452,20454],{"class":77,"line":2815},[75,20451,18687],{"class":190},[75,20453,19847],{"class":92},[75,20455,20456],{"class":92}," 'Mon..Fri 02:00'\n",[75,20458,20459,20461,20464,20467],{"class":77,"line":3092},[75,20460,19858],{"class":190},[75,20462,20463],{"class":171}," --on-active=10s",[75,20465,20466],{"class":92}," /usr/bin/echo",[75,20468,20469],{"class":92}," test\n",[75,20471,20472],{"class":77,"line":3103},[75,20473,527],{"emptyLinePlaceholder":30},[75,20475,20476],{"class":77,"line":3114},[75,20477,20478],{"class":2542},"# Wait-online helpers (pick whichever your system uses)\n",[75,20480,20481,20483,20485],{"class":77,"line":3120},[75,20482,19124],{"class":190},[75,20484,17619],{"class":92},[75,20486,19221],{"class":92},[75,20488,20489,20491,20493],{"class":77,"line":3126},[75,20490,19124],{"class":190},[75,20492,17619],{"class":92},[75,20494,19239],{"class":92},[1414,20496],{},[1737,20498,20500],{"id":20499},"_6-handy-patterns-tiny-recipes","6) Handy Patterns (Tiny Recipes)",[17,20502,20503],{},[1748,20504,20505],{},"Start service only when a path is mounted",[66,20507,20509],{"className":17684,"code":20508,"language":17686,"meta":15,"style":15},"[Unit]\nRequiresMountsFor=/mnt/data\nAfter=network-online.target\nWants=network-online.target\n",[49,20510,20511,20515,20523,20529],{"__ignoreMap":15},[75,20512,20513],{"class":77,"line":78},[75,20514,17693],{"class":190},[75,20516,20517,20520],{"class":77,"line":25},[75,20518,20519],{"class":81},"RequiresMountsFor",[75,20521,20522],{"class":85},"=/mnt/data\n",[75,20524,20525,20527],{"class":77,"line":215},[75,20526,17705],{"class":81},[75,20528,19090],{"class":85},[75,20530,20531,20533],{"class":77,"line":221},[75,20532,19080],{"class":81},[75,20534,19090],{"class":85},[17,20536,20537],{},[1748,20538,20539],{},"One-shot job + timer scaffold (reuse for migrations, syncs, etc.)",[66,20541,20543],{"className":17684,"code":20542,"language":17686,"meta":15,"style":15},"# job.service\n[Service]\nType=oneshot\nExecStart=/usr/local/bin/job.sh\n\n# job.timer\n[Timer]\nOnCalendar=Sun 03:30\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n",[49,20544,20545,20550,20554,20560,20567,20571,20576,20580,20587,20593,20597,20601],{"__ignoreMap":15},[75,20546,20547],{"class":77,"line":78},[75,20548,20549],{"class":2542},"# job.service\n",[75,20551,20552],{"class":77,"line":25},[75,20553,17717],{"class":190},[75,20555,20556,20558],{"class":77,"line":215},[75,20557,18274],{"class":81},[75,20559,19484],{"class":85},[75,20561,20562,20564],{"class":77,"line":221},[75,20563,17745],{"class":81},[75,20565,20566],{"class":85},"=/usr/local/bin/job.sh\n",[75,20568,20569],{"class":77,"line":235},[75,20570,527],{"emptyLinePlaceholder":30},[75,20572,20573],{"class":77,"line":248},[75,20574,20575],{"class":2542},"# job.timer\n",[75,20577,20578],{"class":77,"line":261},[75,20579,19521],{"class":190},[75,20581,20582,20584],{"class":77,"line":274},[75,20583,19526],{"class":81},[75,20585,20586],{"class":85},"=Sun 03:30\n",[75,20588,20589,20591],{"class":77,"line":615},[75,20590,19534],{"class":81},[75,20592,19537],{"class":85},[75,20594,20595],{"class":77,"line":636},[75,20596,527],{"emptyLinePlaceholder":30},[75,20598,20599],{"class":77,"line":642},[75,20600,17765],{"class":190},[75,20602,20603,20605],{"class":77,"line":663},[75,20604,17770],{"class":81},[75,20606,19568],{"class":85},[17,20608,20609],{},[1748,20610,20611],{},"Group app daemons under a stack target",[66,20613,20615],{"className":17684,"code":20614,"language":17686,"meta":15,"style":15},"# my-stack.target\n[Unit]\nDescription=My stack\nWants=api.service worker.service\nAfter=network-online.target\n\n[Install]\nWantedBy=multi-user.target\n",[49,20616,20617,20622,20626,20633,20640,20646,20650,20654],{"__ignoreMap":15},[75,20618,20619],{"class":77,"line":78},[75,20620,20621],{"class":2542},"# my-stack.target\n",[75,20623,20624],{"class":77,"line":25},[75,20625,17693],{"class":190},[75,20627,20628,20630],{"class":77,"line":215},[75,20629,1479],{"class":81},[75,20631,20632],{"class":85},"=My stack\n",[75,20634,20635,20637],{"class":77,"line":221},[75,20636,19080],{"class":81},[75,20638,20639],{"class":85},"=api.service worker.service\n",[75,20641,20642,20644],{"class":77,"line":235},[75,20643,17705],{"class":81},[75,20645,19090],{"class":85},[75,20647,20648],{"class":77,"line":248},[75,20649,527],{"emptyLinePlaceholder":30},[75,20651,20652],{"class":77,"line":261},[75,20653,17765],{"class":190},[75,20655,20656,20658],{"class":77,"line":274},[75,20657,17770],{"class":81},[75,20659,17773],{"class":85},[1697,20661,20662],{},"html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}",{"title":15,"searchDepth":25,"depth":25,"links":20664},[20665,20667,20668,20669,20670,20671,20672,20682,20683,20684,20687,20695,20701,20708,20709,20710],{"id":17677,"depth":25,"text":20666},"Service Unit File (/etc/systemd/system/myapp.service)",{"id":17808,"depth":25,"text":17809},{"id":17837,"depth":25,"text":17838},{"id":17880,"depth":25,"text":17881},{"id":17911,"depth":25,"text":17912},{"id":17941,"depth":25,"text":17942},{"id":17994,"depth":25,"text":17995,"children":20673},[20674,20675,20676,20677,20678,20679,20680,20681],{"id":18008,"depth":215,"text":18009},{"id":18070,"depth":215,"text":18071},{"id":18156,"depth":215,"text":18157},{"id":18217,"depth":215,"text":18218},{"id":18330,"depth":215,"text":18331},{"id":18379,"depth":215,"text":18380},{"id":18530,"depth":215,"text":18531},{"id":18571,"depth":215,"text":18572},{"id":18609,"depth":25,"text":18610},{"id":18676,"depth":25,"text":18677},{"id":18729,"depth":25,"text":18730,"children":20685},[20686],{"id":18765,"depth":215,"text":18766},{"id":18812,"depth":25,"text":18813,"children":20688},[20689,20690,20692,20693,20694],{"id":18816,"depth":215,"text":18817},{"id":18878,"depth":215,"text":20691},"1.2 Options you actually use in a .target",{"id":18987,"depth":215,"text":18988},{"id":19053,"depth":215,"text":19054},{"id":19162,"depth":215,"text":19163},{"id":19255,"depth":25,"text":19256,"children":20696},[20697,20698,20699,20700],{"id":19259,"depth":215,"text":19260},{"id":19295,"depth":215,"text":19296},{"id":19442,"depth":215,"text":19443},{"id":19784,"depth":215,"text":19785},{"id":19875,"depth":25,"text":19876,"children":20702},[20703,20704,20706,20707],{"id":19879,"depth":215,"text":19880},{"id":19977,"depth":215,"text":20705},"3.2 Type= (how systemd decides the service is ready)",{"id":20030,"depth":215,"text":20031},{"id":20098,"depth":215,"text":20099},{"id":20173,"depth":25,"text":20174},{"id":20263,"depth":25,"text":20264},{"id":20499,"depth":25,"text":20500},"This guide is a step-by-step reference on managing a `myapp.service` unit file.","/knowledge-base/social-platforms.png",{},"/knowledge-base/systemd",{"title":17672,"description":20711},{"loc":20714},"knowledge-base/systemd",[1723,20719],"Systemd","xOXQnq2BawQjUP93c6Ie24JvqWOaYZe0DagfjRdsuSc",1784306721699]