[{"data":1,"prerenderedAt":2076},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-protected-routes-guide":3},{"id":4,"title":5,"body":6,"description":2061,"difficulty":2062,"extension":2063,"framework":2064,"frameworkSlug":2065,"meta":2066,"navigation":125,"order":122,"path":2067,"qaPath":2068,"seo":2069,"stem":2070,"subtopic":2071,"topic":2072,"topicSlug":2073,"updated":2074,"__hash__":2075},"blog\u002Fblog\u002Freact-protected-routes-guide.md","React Router v6 Protected Routes — Complete Interview Guide",{"type":7,"value":8,"toc":2052},"minimark",[9,14,27,35,39,66,326,332,501,513,517,534,549,744,766,770,792,1014,1017,1167,1178,1182,1193,1706,1732,1747,1751,1762,1768,1944,1954,1958,2048],[10,11,13],"h2",{"id":12},"why-protected-routes-matter","Why Protected Routes Matter",[15,16,17,18,22,23,26],"p",{},"A React single-page application ships its entire JavaScript bundle to the browser. Without an explicit guard, any visitor who types ",[19,20,21],"code",{},"\u002Fadmin"," or ",[19,24,25],{},"\u002Fdashboard"," into the address bar will see your protected pages — because the browser downloads and executes your JS regardless of who is asking. Client-side routing happens entirely in JavaScript; there is no web server turning away unauthorized requests at the URL level. This means you must explicitly intercept route rendering and redirect unauthenticated users before their component mounts.",[15,28,29,30,34],{},"The second reason protected routes matter is developer experience and architecture. Rather than sprinkling auth checks across every page component, a ",[31,32,33],"strong",{},"centralized guard"," acts as middleware — one place that enforces access policy for an entire subtree of routes. Interviewers ask about this pattern because it touches core React concepts (context, composition, rendering) as well as security reasoning (client-side vs. server-side trust boundaries). Knowing it cold signals that you can build production-grade auth, not just hello-world tutorials.",[10,36,38],{"id":37},"the-requireauth-wrapper","The RequireAuth Wrapper",[15,40,41,42,45,46,49,50,53,54,57,58,61,62,65],{},"React Router v6's ",[31,43,44],{},"layout route"," pattern is the cleanest way to guard routes. A layout route is a ",[19,47,48],{},"\u003CRoute>"," with an ",[19,51,52],{},"element"," but no ",[19,55,56],{},"path"," — it renders for any matched child route. Combine this with ",[19,59,60],{},"\u003CNavigate>"," for declarative redirection and ",[19,63,64],{},"\u003COutlet>"," to pass through to children.",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-jsx shiki shiki-themes github-light github-dark","\u002F\u002F guards\u002FRequireAuth.jsx\nimport { Navigate, Outlet, useLocation } from 'react-router-dom';\nimport { useAuth } from '..\u002Fcontext\u002FAuthContext';\n\nexport function RequireAuth() {\n  const { user, loading } = useAuth();\n  const location = useLocation(); \u002F\u002F capture current URL before redirecting\n\n  \u002F\u002F Wait for silent-refresh to complete — prevents premature redirect\n  if (loading) return \u003Cdiv aria-label=\"Checking auth…\" \u002F>;\n\n  if (!user) {\n    \u002F\u002F Pass current location so login can redirect back after success\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>; \u002F\u002F authenticated — render matched child route\n}\n","jsx","",[19,74,75,84,105,120,127,143,174,194,199,205,235,240,254,260,292,298,303,320],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sJ8bj","\u002F\u002F guards\u002FRequireAuth.jsx\n",[76,85,87,91,95,98,102],{"class":78,"line":86},2,[76,88,90],{"class":89},"szBVR","import",[76,92,94],{"class":93},"sVt8B"," { Navigate, Outlet, useLocation } ",[76,96,97],{"class":89},"from",[76,99,101],{"class":100},"sZZnC"," 'react-router-dom'",[76,103,104],{"class":93},";\n",[76,106,108,110,113,115,118],{"class":78,"line":107},3,[76,109,90],{"class":89},[76,111,112],{"class":93}," { useAuth } ",[76,114,97],{"class":89},[76,116,117],{"class":100}," '..\u002Fcontext\u002FAuthContext'",[76,119,104],{"class":93},[76,121,123],{"class":78,"line":122},4,[76,124,126],{"emptyLinePlaceholder":125},true,"\n",[76,128,130,133,136,140],{"class":78,"line":129},5,[76,131,132],{"class":89},"export",[76,134,135],{"class":89}," function",[76,137,139],{"class":138},"sScJk"," RequireAuth",[76,141,142],{"class":93},"() {\n",[76,144,146,149,152,156,159,162,165,168,171],{"class":78,"line":145},6,[76,147,148],{"class":89},"  const",[76,150,151],{"class":93}," { ",[76,153,155],{"class":154},"sj4cs","user",[76,157,158],{"class":93},", ",[76,160,161],{"class":154},"loading",[76,163,164],{"class":93}," } ",[76,166,167],{"class":89},"=",[76,169,170],{"class":138}," useAuth",[76,172,173],{"class":93},"();\n",[76,175,177,179,182,185,188,191],{"class":78,"line":176},7,[76,178,148],{"class":89},[76,180,181],{"class":154}," location",[76,183,184],{"class":89}," =",[76,186,187],{"class":138}," useLocation",[76,189,190],{"class":93},"(); ",[76,192,193],{"class":82},"\u002F\u002F capture current URL before redirecting\n",[76,195,197],{"class":78,"line":196},8,[76,198,126],{"emptyLinePlaceholder":125},[76,200,202],{"class":78,"line":201},9,[76,203,204],{"class":82},"  \u002F\u002F Wait for silent-refresh to complete — prevents premature redirect\n",[76,206,208,211,214,217,220,224,227,229,232],{"class":78,"line":207},10,[76,209,210],{"class":89},"  if",[76,212,213],{"class":93}," (loading) ",[76,215,216],{"class":89},"return",[76,218,219],{"class":93}," \u003C",[76,221,223],{"class":222},"s9eBZ","div",[76,225,226],{"class":138}," aria-label",[76,228,167],{"class":89},[76,230,231],{"class":100},"\"Checking auth…\"",[76,233,234],{"class":93}," \u002F>;\n",[76,236,238],{"class":78,"line":237},11,[76,239,126],{"emptyLinePlaceholder":125},[76,241,243,245,248,251],{"class":78,"line":242},12,[76,244,210],{"class":89},[76,246,247],{"class":93}," (",[76,249,250],{"class":89},"!",[76,252,253],{"class":93},"user) {\n",[76,255,257],{"class":78,"line":256},13,[76,258,259],{"class":82},"    \u002F\u002F Pass current location so login can redirect back after success\n",[76,261,263,266,268,271,274,276,279,282,284,287,290],{"class":78,"line":262},14,[76,264,265],{"class":89},"    return",[76,267,219],{"class":93},[76,269,270],{"class":154},"Navigate",[76,272,273],{"class":138}," to",[76,275,167],{"class":89},[76,277,278],{"class":100},"\"\u002Flogin\"",[76,280,281],{"class":138}," state",[76,283,167],{"class":89},[76,285,286],{"class":93},"{{ from: location }} ",[76,288,289],{"class":138},"replace",[76,291,234],{"class":93},[76,293,295],{"class":78,"line":294},15,[76,296,297],{"class":93},"  }\n",[76,299,301],{"class":78,"line":300},16,[76,302,126],{"emptyLinePlaceholder":125},[76,304,306,309,311,314,317],{"class":78,"line":305},17,[76,307,308],{"class":89},"  return",[76,310,219],{"class":93},[76,312,313],{"class":154},"Outlet",[76,315,316],{"class":93}," \u002F>; ",[76,318,319],{"class":82},"\u002F\u002F authenticated — render matched child route\n",[76,321,323],{"class":78,"line":322},18,[76,324,325],{"class":93},"}\n",[15,327,328,329,331],{},"Wire it into your router by nesting protected routes beneath it with no ",[19,330,56],{}," on the guard itself:",[67,333,335],{"className":69,"code":334,"language":71,"meta":72,"style":72},"\u003CRoutes>\n  \u003CRoute path=\"\u002F\" element={\u003CHome \u002F>} \u002F>\n  \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F>   {\u002F* must be outside *\u002F}\n\n  \u003CRoute element={\u003CRequireAuth \u002F>}>                 {\u002F* layout guard *\u002F}\n    \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n    \u003CRoute path=\"\u002Fsettings\" element={\u003CSettings \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n",[19,336,337,348,378,407,411,434,459,483,492],{"__ignoreMap":72},[76,338,339,342,345],{"class":78,"line":79},[76,340,341],{"class":93},"\u003C",[76,343,344],{"class":154},"Routes",[76,346,347],{"class":93},">\n",[76,349,350,353,356,359,361,364,367,369,372,375],{"class":78,"line":86},[76,351,352],{"class":93},"  \u003C",[76,354,355],{"class":154},"Route",[76,357,358],{"class":138}," path",[76,360,167],{"class":89},[76,362,363],{"class":100},"\"\u002F\"",[76,365,366],{"class":138}," element",[76,368,167],{"class":89},[76,370,371],{"class":93},"{\u003C",[76,373,374],{"class":154},"Home",[76,376,377],{"class":93}," \u002F>} \u002F>\n",[76,379,380,382,384,386,388,390,392,394,396,399,402,405],{"class":78,"line":107},[76,381,352],{"class":93},[76,383,355],{"class":154},[76,385,358],{"class":138},[76,387,167],{"class":89},[76,389,278],{"class":100},[76,391,366],{"class":138},[76,393,167],{"class":89},[76,395,371],{"class":93},[76,397,398],{"class":154},"LoginPage",[76,400,401],{"class":93}," \u002F>} \u002F>   {",[76,403,404],{"class":82},"\u002F* must be outside *\u002F",[76,406,325],{"class":93},[76,408,409],{"class":78,"line":122},[76,410,126],{"emptyLinePlaceholder":125},[76,412,413,415,417,419,421,423,426,429,432],{"class":78,"line":129},[76,414,352],{"class":93},[76,416,355],{"class":154},[76,418,366],{"class":138},[76,420,167],{"class":89},[76,422,371],{"class":93},[76,424,425],{"class":154},"RequireAuth",[76,427,428],{"class":93}," \u002F>}>                 {",[76,430,431],{"class":82},"\u002F* layout guard *\u002F",[76,433,325],{"class":93},[76,435,436,439,441,443,445,448,450,452,454,457],{"class":78,"line":145},[76,437,438],{"class":93},"    \u003C",[76,440,355],{"class":154},[76,442,358],{"class":138},[76,444,167],{"class":89},[76,446,447],{"class":100},"\"\u002Fdashboard\"",[76,449,366],{"class":138},[76,451,167],{"class":89},[76,453,371],{"class":93},[76,455,456],{"class":154},"Dashboard",[76,458,377],{"class":93},[76,460,461,463,465,467,469,472,474,476,478,481],{"class":78,"line":176},[76,462,438],{"class":93},[76,464,355],{"class":154},[76,466,358],{"class":138},[76,468,167],{"class":89},[76,470,471],{"class":100},"\"\u002Fsettings\"",[76,473,366],{"class":138},[76,475,167],{"class":89},[76,477,371],{"class":93},[76,479,480],{"class":154},"Settings",[76,482,377],{"class":93},[76,484,485,488,490],{"class":78,"line":196},[76,486,487],{"class":93},"  \u003C\u002F",[76,489,355],{"class":154},[76,491,347],{"class":93},[76,493,494,497,499],{"class":78,"line":201},[76,495,496],{"class":93},"\u003C\u002F",[76,498,344],{"class":154},[76,500,347],{"class":93},[15,502,503,504,506,507,509,510,512],{},"Two details matter here. First, ",[19,505,289],{}," on ",[19,508,60],{}," overwrites the history entry rather than pushing — without it, clicking Back after login sends the user to the protected URL again, which redirects again, creating a loop. Second, the ",[19,511,161],{}," check is mandatory if auth state is restored asynchronously (silent refresh, cookie validation) — without it the guard fires before it knows the answer and redirects every valid user to login on a hard refresh.",[10,514,516],{"id":515},"redirect-after-login-preserving-the-intended-destination","Redirect-After-Login: Preserving the Intended Destination",[15,518,519,520,522,523,526,527,529,530,533],{},"When a user bookmarks ",[19,521,25],{},", closes the tab, reopens it, and gets sent to ",[19,524,525],{},"\u002Flogin",", they expect to land back on ",[19,528,25],{}," after logging in — not a generic home page. The mechanism is ",[31,531,532],{},"React Router's location state",".",[15,535,536,537,540,541,544,545,548],{},"The guard passes the current ",[19,538,539],{},"location"," object in the redirect's ",[19,542,543],{},"state.from"," field. The login page reads that value after a successful login and calls ",[19,546,547],{},"navigate()"," with it:",[67,550,552],{"className":69,"code":551,"language":71,"meta":72,"style":72},"\u002F\u002F pages\u002FLoginPage.jsx\nimport { useNavigate, useLocation } from 'react-router-dom';\nimport { useAuth } from '..\u002Fcontext\u002FAuthContext';\n\nexport function LoginPage() {\n  const { login } = useAuth();\n  const navigate = useNavigate();\n  const location = useLocation();\n\n  \u002F\u002F Fall back to \u002Fdashboard if user navigated directly to \u002Flogin\n  const from = location.state?.from?.pathname ?? '\u002Fdashboard';\n\n  async function handleSubmit(credentials) {\n    await login(credentials);            \u002F\u002F authenticate with server\n    navigate(from, { replace: true });   \u002F\u002F go to original destination\n  }\n\n  \u002F\u002F render form…\n}\n",[19,553,554,559,572,584,588,599,616,630,642,646,651,671,675,695,709,726,730,734,739],{"__ignoreMap":72},[76,555,556],{"class":78,"line":79},[76,557,558],{"class":82},"\u002F\u002F pages\u002FLoginPage.jsx\n",[76,560,561,563,566,568,570],{"class":78,"line":86},[76,562,90],{"class":89},[76,564,565],{"class":93}," { useNavigate, useLocation } ",[76,567,97],{"class":89},[76,569,101],{"class":100},[76,571,104],{"class":93},[76,573,574,576,578,580,582],{"class":78,"line":107},[76,575,90],{"class":89},[76,577,112],{"class":93},[76,579,97],{"class":89},[76,581,117],{"class":100},[76,583,104],{"class":93},[76,585,586],{"class":78,"line":122},[76,587,126],{"emptyLinePlaceholder":125},[76,589,590,592,594,597],{"class":78,"line":129},[76,591,132],{"class":89},[76,593,135],{"class":89},[76,595,596],{"class":138}," LoginPage",[76,598,142],{"class":93},[76,600,601,603,605,608,610,612,614],{"class":78,"line":145},[76,602,148],{"class":89},[76,604,151],{"class":93},[76,606,607],{"class":154},"login",[76,609,164],{"class":93},[76,611,167],{"class":89},[76,613,170],{"class":138},[76,615,173],{"class":93},[76,617,618,620,623,625,628],{"class":78,"line":176},[76,619,148],{"class":89},[76,621,622],{"class":154}," navigate",[76,624,184],{"class":89},[76,626,627],{"class":138}," useNavigate",[76,629,173],{"class":93},[76,631,632,634,636,638,640],{"class":78,"line":196},[76,633,148],{"class":89},[76,635,181],{"class":154},[76,637,184],{"class":89},[76,639,187],{"class":138},[76,641,173],{"class":93},[76,643,644],{"class":78,"line":201},[76,645,126],{"emptyLinePlaceholder":125},[76,647,648],{"class":78,"line":207},[76,649,650],{"class":82},"  \u002F\u002F Fall back to \u002Fdashboard if user navigated directly to \u002Flogin\n",[76,652,653,655,658,660,663,666,669],{"class":78,"line":237},[76,654,148],{"class":89},[76,656,657],{"class":154}," from",[76,659,184],{"class":89},[76,661,662],{"class":93}," location.state?.from?.pathname ",[76,664,665],{"class":89},"??",[76,667,668],{"class":100}," '\u002Fdashboard'",[76,670,104],{"class":93},[76,672,673],{"class":78,"line":242},[76,674,126],{"emptyLinePlaceholder":125},[76,676,677,680,682,685,688,692],{"class":78,"line":256},[76,678,679],{"class":89},"  async",[76,681,135],{"class":89},[76,683,684],{"class":138}," handleSubmit",[76,686,687],{"class":93},"(",[76,689,691],{"class":690},"s4XuR","credentials",[76,693,694],{"class":93},") {\n",[76,696,697,700,703,706],{"class":78,"line":262},[76,698,699],{"class":89},"    await",[76,701,702],{"class":138}," login",[76,704,705],{"class":93},"(credentials);            ",[76,707,708],{"class":82},"\u002F\u002F authenticate with server\n",[76,710,711,714,717,720,723],{"class":78,"line":294},[76,712,713],{"class":138},"    navigate",[76,715,716],{"class":93},"(from, { replace: ",[76,718,719],{"class":154},"true",[76,721,722],{"class":93}," });   ",[76,724,725],{"class":82},"\u002F\u002F go to original destination\n",[76,727,728],{"class":78,"line":300},[76,729,297],{"class":93},[76,731,732],{"class":78,"line":305},[76,733,126],{"emptyLinePlaceholder":125},[76,735,736],{"class":78,"line":322},[76,737,738],{"class":82},"  \u002F\u002F render form…\n",[76,740,742],{"class":78,"line":741},19,[76,743,325],{"class":93},[15,745,746,747,750,751,753,754,757,758,761,762,765],{},"The ",[19,748,749],{},"?? '\u002Fdashboard'"," fallback is critical — if the user navigates directly to ",[19,752,525],{}," there is no ",[19,755,756],{},"state",", and reading ",[19,759,760],{},"location.state?.from?.pathname"," returns ",[19,763,764],{},"undefined",". Always provide a sensible default.",[10,767,769],{"id":768},"role-based-access-control","Role-Based Access Control",[15,771,772,773,776,777,779,780,783,784,787,788,791],{},"Authentication answers \"who are you?\"; ",[31,774,775],{},"authorization"," answers \"what are you allowed to do?\" Extend ",[19,778,425],{}," with a ",[19,781,782],{},"RequireRole"," guard that checks ",[19,785,786],{},"user.role"," against an ",[19,789,790],{},"allowedRoles"," array:",[67,793,795],{"className":69,"code":794,"language":71,"meta":72,"style":72},"\u002F\u002F guards\u002FRequireRole.jsx\nimport { Navigate, Outlet, useLocation } from 'react-router-dom';\nimport { useAuth } from '..\u002Fcontext\u002FAuthContext';\n\nexport function RequireRole({ allowedRoles }) {\n  const { user, loading } = useAuth();\n  const location = useLocation();\n\n  if (loading) return null;\n\n  if (!user) {\n    \u002F\u002F Not logged in → send to login (don't reveal the page exists)\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  if (!allowedRoles.includes(user.role)) {\n    \u002F\u002F Logged in but wrong role → send to forbidden page\n    return \u003CNavigate to=\"\u002F403\" replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>;\n}\n",[19,796,797,802,814,826,830,847,867,879,883,896,900,910,915,939,943,947,964,969,989,993,998,1009],{"__ignoreMap":72},[76,798,799],{"class":78,"line":79},[76,800,801],{"class":82},"\u002F\u002F guards\u002FRequireRole.jsx\n",[76,803,804,806,808,810,812],{"class":78,"line":86},[76,805,90],{"class":89},[76,807,94],{"class":93},[76,809,97],{"class":89},[76,811,101],{"class":100},[76,813,104],{"class":93},[76,815,816,818,820,822,824],{"class":78,"line":107},[76,817,90],{"class":89},[76,819,112],{"class":93},[76,821,97],{"class":89},[76,823,117],{"class":100},[76,825,104],{"class":93},[76,827,828],{"class":78,"line":122},[76,829,126],{"emptyLinePlaceholder":125},[76,831,832,834,836,839,842,844],{"class":78,"line":129},[76,833,132],{"class":89},[76,835,135],{"class":89},[76,837,838],{"class":138}," RequireRole",[76,840,841],{"class":93},"({ ",[76,843,790],{"class":690},[76,845,846],{"class":93}," }) {\n",[76,848,849,851,853,855,857,859,861,863,865],{"class":78,"line":145},[76,850,148],{"class":89},[76,852,151],{"class":93},[76,854,155],{"class":154},[76,856,158],{"class":93},[76,858,161],{"class":154},[76,860,164],{"class":93},[76,862,167],{"class":89},[76,864,170],{"class":138},[76,866,173],{"class":93},[76,868,869,871,873,875,877],{"class":78,"line":176},[76,870,148],{"class":89},[76,872,181],{"class":154},[76,874,184],{"class":89},[76,876,187],{"class":138},[76,878,173],{"class":93},[76,880,881],{"class":78,"line":196},[76,882,126],{"emptyLinePlaceholder":125},[76,884,885,887,889,891,894],{"class":78,"line":201},[76,886,210],{"class":89},[76,888,213],{"class":93},[76,890,216],{"class":89},[76,892,893],{"class":154}," null",[76,895,104],{"class":93},[76,897,898],{"class":78,"line":207},[76,899,126],{"emptyLinePlaceholder":125},[76,901,902,904,906,908],{"class":78,"line":237},[76,903,210],{"class":89},[76,905,247],{"class":93},[76,907,250],{"class":89},[76,909,253],{"class":93},[76,911,912],{"class":78,"line":242},[76,913,914],{"class":82},"    \u002F\u002F Not logged in → send to login (don't reveal the page exists)\n",[76,916,917,919,921,923,925,927,929,931,933,935,937],{"class":78,"line":256},[76,918,265],{"class":89},[76,920,219],{"class":93},[76,922,270],{"class":154},[76,924,273],{"class":138},[76,926,167],{"class":89},[76,928,278],{"class":100},[76,930,281],{"class":138},[76,932,167],{"class":89},[76,934,286],{"class":93},[76,936,289],{"class":138},[76,938,234],{"class":93},[76,940,941],{"class":78,"line":262},[76,942,297],{"class":93},[76,944,945],{"class":78,"line":294},[76,946,126],{"emptyLinePlaceholder":125},[76,948,949,951,953,955,958,961],{"class":78,"line":300},[76,950,210],{"class":89},[76,952,247],{"class":93},[76,954,250],{"class":89},[76,956,957],{"class":93},"allowedRoles.",[76,959,960],{"class":138},"includes",[76,962,963],{"class":93},"(user.role)) {\n",[76,965,966],{"class":78,"line":305},[76,967,968],{"class":82},"    \u002F\u002F Logged in but wrong role → send to forbidden page\n",[76,970,971,973,975,977,979,981,984,987],{"class":78,"line":322},[76,972,265],{"class":89},[76,974,219],{"class":93},[76,976,270],{"class":154},[76,978,273],{"class":138},[76,980,167],{"class":89},[76,982,983],{"class":100},"\"\u002F403\"",[76,985,986],{"class":138}," replace",[76,988,234],{"class":93},[76,990,991],{"class":78,"line":741},[76,992,297],{"class":93},[76,994,996],{"class":78,"line":995},20,[76,997,126],{"emptyLinePlaceholder":125},[76,999,1001,1003,1005,1007],{"class":78,"line":1000},21,[76,1002,308],{"class":89},[76,1004,219],{"class":93},[76,1006,313],{"class":154},[76,1008,234],{"class":93},[76,1010,1012],{"class":78,"line":1011},22,[76,1013,325],{"class":93},[15,1015,1016],{},"Stack guards by nesting layout routes — each layer checks one concern:",[67,1018,1020],{"className":69,"code":1019,"language":71,"meta":72,"style":72},"\u003CRoute element={\u003CRequireAuth \u002F>}>              {\u002F* layer 1: logged in *\u002F}\n  \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n\n  \u003CRoute element={\u003CRequireRole allowedRoles={['admin']} \u002F>}> {\u002F* layer 2: role *\u002F}\n    \u003CRoute path=\"\u002Fadmin\" element={\u003CAdminPanel \u002F>} \u002F>\n    \u003CRoute path=\"\u002Fadmin\u002Fusers\" element={\u003CUserManager \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoute>\n",[19,1021,1022,1044,1066,1070,1103,1127,1151,1159],{"__ignoreMap":72},[76,1023,1024,1026,1028,1030,1032,1034,1036,1039,1042],{"class":78,"line":79},[76,1025,341],{"class":93},[76,1027,355],{"class":154},[76,1029,366],{"class":138},[76,1031,167],{"class":89},[76,1033,371],{"class":93},[76,1035,425],{"class":154},[76,1037,1038],{"class":93}," \u002F>}>              {",[76,1040,1041],{"class":82},"\u002F* layer 1: logged in *\u002F",[76,1043,325],{"class":93},[76,1045,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064],{"class":78,"line":86},[76,1047,352],{"class":93},[76,1049,355],{"class":154},[76,1051,358],{"class":138},[76,1053,167],{"class":89},[76,1055,447],{"class":100},[76,1057,366],{"class":138},[76,1059,167],{"class":89},[76,1061,371],{"class":93},[76,1063,456],{"class":154},[76,1065,377],{"class":93},[76,1067,1068],{"class":78,"line":107},[76,1069,126],{"emptyLinePlaceholder":125},[76,1071,1072,1074,1076,1078,1080,1082,1084,1087,1089,1092,1095,1098,1101],{"class":78,"line":122},[76,1073,352],{"class":93},[76,1075,355],{"class":154},[76,1077,366],{"class":138},[76,1079,167],{"class":89},[76,1081,371],{"class":93},[76,1083,782],{"class":154},[76,1085,1086],{"class":138}," allowedRoles",[76,1088,167],{"class":89},[76,1090,1091],{"class":93},"{[",[76,1093,1094],{"class":100},"'admin'",[76,1096,1097],{"class":93},"]} \u002F>}> {",[76,1099,1100],{"class":82},"\u002F* layer 2: role *\u002F",[76,1102,325],{"class":93},[76,1104,1105,1107,1109,1111,1113,1116,1118,1120,1122,1125],{"class":78,"line":129},[76,1106,438],{"class":93},[76,1108,355],{"class":154},[76,1110,358],{"class":138},[76,1112,167],{"class":89},[76,1114,1115],{"class":100},"\"\u002Fadmin\"",[76,1117,366],{"class":138},[76,1119,167],{"class":89},[76,1121,371],{"class":93},[76,1123,1124],{"class":154},"AdminPanel",[76,1126,377],{"class":93},[76,1128,1129,1131,1133,1135,1137,1140,1142,1144,1146,1149],{"class":78,"line":145},[76,1130,438],{"class":93},[76,1132,355],{"class":154},[76,1134,358],{"class":138},[76,1136,167],{"class":89},[76,1138,1139],{"class":100},"\"\u002Fadmin\u002Fusers\"",[76,1141,366],{"class":138},[76,1143,167],{"class":89},[76,1145,371],{"class":93},[76,1147,1148],{"class":154},"UserManager",[76,1150,377],{"class":93},[76,1152,1153,1155,1157],{"class":78,"line":176},[76,1154,487],{"class":93},[76,1156,355],{"class":154},[76,1158,347],{"class":93},[76,1160,1161,1163,1165],{"class":78,"line":196},[76,1162,496],{"class":93},[76,1164,355],{"class":154},[76,1166,347],{"class":93},[15,1168,1169,1170,1173,1174,1177],{},"Keep the two error destinations distinct: unauthenticated users should see the ",[31,1171,1172],{},"login page"," (they might have a valid account); authenticated users with the wrong role should see ",[31,1175,1176],{},"403"," (logging in again won't help). Conflating them creates confusing UX.",[10,1179,1181],{"id":1180},"the-authcontext-useauth-hook-pattern","The AuthContext + useAuth Hook Pattern",[15,1183,1184,1185,1188,1189,1192],{},"Route guards are only as good as the auth state they read. ",[31,1186,1187],{},"AuthContext"," centralizes that state — user object, loading flag, login, and logout — and exposes it via a ",[19,1190,1191],{},"useAuth"," hook that throws if used outside the provider.",[67,1194,1196],{"className":69,"code":1195,"language":71,"meta":72,"style":72},"\u002F\u002F context\u002FAuthContext.jsx\nimport { createContext, useContext, useState, useEffect } from 'react';\nimport { api } from '..\u002Flib\u002Fapi';\n\nconst AuthContext = createContext(null);\n\nexport function AuthProvider({ children }) {\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true); \u002F\u002F true until session is checked\n\n  useEffect(() => {\n    \u002F\u002F Restore session from httpOnly refresh-token cookie on every page load\n    api.post('\u002Fauth\u002Frefresh')\n      .then(({ data }) => setUser(data.user))\n      .catch(() => setUser(null))\n      .finally(() => setLoading(false)); \u002F\u002F guards can now decide\n  }, []);\n\n  const login = async (credentials) => {\n    const { data } = await api.post('\u002Fauth\u002Flogin', credentials);\n    setUser(data.user);\n  };\n\n  const logout = async () => {\n    await api.post('\u002Fauth\u002Flogout');\n    setUser(null);\n  };\n\n  return (\n    \u003CAuthContext.Provider value={{ user, loading, login, logout }}>\n      {children}\n    \u003C\u002FAuthContext.Provider>\n  );\n}\n\n\u002F\u002F Throws a clear error if called outside provider — surfaces bugs immediately\nexport function useAuth() {\n  const ctx = useContext(AuthContext);\n  if (!ctx) throw new Error('useAuth must be used inside \u003CAuthProvider>');\n  return ctx;\n}\n",[19,1197,1198,1203,1217,1231,1235,1256,1260,1276,1304,1333,1337,1351,1356,1372,1397,1417,1442,1447,1451,1473,1502,1510,1515,1520,1539,1555,1566,1571,1576,1584,1600,1606,1616,1622,1627,1632,1638,1649,1665,1693,1701],{"__ignoreMap":72},[76,1199,1200],{"class":78,"line":79},[76,1201,1202],{"class":82},"\u002F\u002F context\u002FAuthContext.jsx\n",[76,1204,1205,1207,1210,1212,1215],{"class":78,"line":86},[76,1206,90],{"class":89},[76,1208,1209],{"class":93}," { createContext, useContext, useState, useEffect } ",[76,1211,97],{"class":89},[76,1213,1214],{"class":100}," 'react'",[76,1216,104],{"class":93},[76,1218,1219,1221,1224,1226,1229],{"class":78,"line":107},[76,1220,90],{"class":89},[76,1222,1223],{"class":93}," { api } ",[76,1225,97],{"class":89},[76,1227,1228],{"class":100}," '..\u002Flib\u002Fapi'",[76,1230,104],{"class":93},[76,1232,1233],{"class":78,"line":122},[76,1234,126],{"emptyLinePlaceholder":125},[76,1236,1237,1240,1243,1245,1248,1250,1253],{"class":78,"line":129},[76,1238,1239],{"class":89},"const",[76,1241,1242],{"class":154}," AuthContext",[76,1244,184],{"class":89},[76,1246,1247],{"class":138}," createContext",[76,1249,687],{"class":93},[76,1251,1252],{"class":154},"null",[76,1254,1255],{"class":93},");\n",[76,1257,1258],{"class":78,"line":145},[76,1259,126],{"emptyLinePlaceholder":125},[76,1261,1262,1264,1266,1269,1271,1274],{"class":78,"line":176},[76,1263,132],{"class":89},[76,1265,135],{"class":89},[76,1267,1268],{"class":138}," AuthProvider",[76,1270,841],{"class":93},[76,1272,1273],{"class":690},"children",[76,1275,846],{"class":93},[76,1277,1278,1280,1283,1285,1287,1290,1293,1295,1298,1300,1302],{"class":78,"line":196},[76,1279,148],{"class":89},[76,1281,1282],{"class":93}," [",[76,1284,155],{"class":154},[76,1286,158],{"class":93},[76,1288,1289],{"class":154},"setUser",[76,1291,1292],{"class":93},"] ",[76,1294,167],{"class":89},[76,1296,1297],{"class":138}," useState",[76,1299,687],{"class":93},[76,1301,1252],{"class":154},[76,1303,1255],{"class":93},[76,1305,1306,1308,1310,1312,1314,1317,1319,1321,1323,1325,1327,1330],{"class":78,"line":201},[76,1307,148],{"class":89},[76,1309,1282],{"class":93},[76,1311,161],{"class":154},[76,1313,158],{"class":93},[76,1315,1316],{"class":154},"setLoading",[76,1318,1292],{"class":93},[76,1320,167],{"class":89},[76,1322,1297],{"class":138},[76,1324,687],{"class":93},[76,1326,719],{"class":154},[76,1328,1329],{"class":93},"); ",[76,1331,1332],{"class":82},"\u002F\u002F true until session is checked\n",[76,1334,1335],{"class":78,"line":207},[76,1336,126],{"emptyLinePlaceholder":125},[76,1338,1339,1342,1345,1348],{"class":78,"line":237},[76,1340,1341],{"class":138},"  useEffect",[76,1343,1344],{"class":93},"(() ",[76,1346,1347],{"class":89},"=>",[76,1349,1350],{"class":93}," {\n",[76,1352,1353],{"class":78,"line":242},[76,1354,1355],{"class":82},"    \u002F\u002F Restore session from httpOnly refresh-token cookie on every page load\n",[76,1357,1358,1361,1364,1366,1369],{"class":78,"line":256},[76,1359,1360],{"class":93},"    api.",[76,1362,1363],{"class":138},"post",[76,1365,687],{"class":93},[76,1367,1368],{"class":100},"'\u002Fauth\u002Frefresh'",[76,1370,1371],{"class":93},")\n",[76,1373,1374,1377,1380,1383,1386,1389,1391,1394],{"class":78,"line":262},[76,1375,1376],{"class":93},"      .",[76,1378,1379],{"class":138},"then",[76,1381,1382],{"class":93},"(({ ",[76,1384,1385],{"class":690},"data",[76,1387,1388],{"class":93}," }) ",[76,1390,1347],{"class":89},[76,1392,1393],{"class":138}," setUser",[76,1395,1396],{"class":93},"(data.user))\n",[76,1398,1399,1401,1404,1406,1408,1410,1412,1414],{"class":78,"line":294},[76,1400,1376],{"class":93},[76,1402,1403],{"class":138},"catch",[76,1405,1344],{"class":93},[76,1407,1347],{"class":89},[76,1409,1393],{"class":138},[76,1411,687],{"class":93},[76,1413,1252],{"class":154},[76,1415,1416],{"class":93},"))\n",[76,1418,1419,1421,1424,1426,1428,1431,1433,1436,1439],{"class":78,"line":300},[76,1420,1376],{"class":93},[76,1422,1423],{"class":138},"finally",[76,1425,1344],{"class":93},[76,1427,1347],{"class":89},[76,1429,1430],{"class":138}," setLoading",[76,1432,687],{"class":93},[76,1434,1435],{"class":154},"false",[76,1437,1438],{"class":93},")); ",[76,1440,1441],{"class":82},"\u002F\u002F guards can now decide\n",[76,1443,1444],{"class":78,"line":305},[76,1445,1446],{"class":93},"  }, []);\n",[76,1448,1449],{"class":78,"line":322},[76,1450,126],{"emptyLinePlaceholder":125},[76,1452,1453,1455,1457,1459,1462,1464,1466,1469,1471],{"class":78,"line":741},[76,1454,148],{"class":89},[76,1456,702],{"class":138},[76,1458,184],{"class":89},[76,1460,1461],{"class":89}," async",[76,1463,247],{"class":93},[76,1465,691],{"class":690},[76,1467,1468],{"class":93},") ",[76,1470,1347],{"class":89},[76,1472,1350],{"class":93},[76,1474,1475,1478,1480,1482,1484,1486,1489,1492,1494,1496,1499],{"class":78,"line":995},[76,1476,1477],{"class":89},"    const",[76,1479,151],{"class":93},[76,1481,1385],{"class":154},[76,1483,164],{"class":93},[76,1485,167],{"class":89},[76,1487,1488],{"class":89}," await",[76,1490,1491],{"class":93}," api.",[76,1493,1363],{"class":138},[76,1495,687],{"class":93},[76,1497,1498],{"class":100},"'\u002Fauth\u002Flogin'",[76,1500,1501],{"class":93},", credentials);\n",[76,1503,1504,1507],{"class":78,"line":1000},[76,1505,1506],{"class":138},"    setUser",[76,1508,1509],{"class":93},"(data.user);\n",[76,1511,1512],{"class":78,"line":1011},[76,1513,1514],{"class":93},"  };\n",[76,1516,1518],{"class":78,"line":1517},23,[76,1519,126],{"emptyLinePlaceholder":125},[76,1521,1523,1525,1528,1530,1532,1535,1537],{"class":78,"line":1522},24,[76,1524,148],{"class":89},[76,1526,1527],{"class":138}," logout",[76,1529,184],{"class":89},[76,1531,1461],{"class":89},[76,1533,1534],{"class":93}," () ",[76,1536,1347],{"class":89},[76,1538,1350],{"class":93},[76,1540,1542,1544,1546,1548,1550,1553],{"class":78,"line":1541},25,[76,1543,699],{"class":89},[76,1545,1491],{"class":93},[76,1547,1363],{"class":138},[76,1549,687],{"class":93},[76,1551,1552],{"class":100},"'\u002Fauth\u002Flogout'",[76,1554,1255],{"class":93},[76,1556,1558,1560,1562,1564],{"class":78,"line":1557},26,[76,1559,1506],{"class":138},[76,1561,687],{"class":93},[76,1563,1252],{"class":154},[76,1565,1255],{"class":93},[76,1567,1569],{"class":78,"line":1568},27,[76,1570,1514],{"class":93},[76,1572,1574],{"class":78,"line":1573},28,[76,1575,126],{"emptyLinePlaceholder":125},[76,1577,1579,1581],{"class":78,"line":1578},29,[76,1580,308],{"class":89},[76,1582,1583],{"class":93}," (\n",[76,1585,1587,1589,1592,1595,1597],{"class":78,"line":1586},30,[76,1588,438],{"class":93},[76,1590,1591],{"class":154},"AuthContext.Provider",[76,1593,1594],{"class":138}," value",[76,1596,167],{"class":89},[76,1598,1599],{"class":93},"{{ user, loading, login, logout }}>\n",[76,1601,1603],{"class":78,"line":1602},31,[76,1604,1605],{"class":93},"      {children}\n",[76,1607,1609,1612,1614],{"class":78,"line":1608},32,[76,1610,1611],{"class":93},"    \u003C\u002F",[76,1613,1591],{"class":154},[76,1615,347],{"class":93},[76,1617,1619],{"class":78,"line":1618},33,[76,1620,1621],{"class":93},"  );\n",[76,1623,1625],{"class":78,"line":1624},34,[76,1626,325],{"class":93},[76,1628,1630],{"class":78,"line":1629},35,[76,1631,126],{"emptyLinePlaceholder":125},[76,1633,1635],{"class":78,"line":1634},36,[76,1636,1637],{"class":82},"\u002F\u002F Throws a clear error if called outside provider — surfaces bugs immediately\n",[76,1639,1641,1643,1645,1647],{"class":78,"line":1640},37,[76,1642,132],{"class":89},[76,1644,135],{"class":89},[76,1646,170],{"class":138},[76,1648,142],{"class":93},[76,1650,1652,1654,1657,1659,1662],{"class":78,"line":1651},38,[76,1653,148],{"class":89},[76,1655,1656],{"class":154}," ctx",[76,1658,184],{"class":89},[76,1660,1661],{"class":138}," useContext",[76,1663,1664],{"class":93},"(AuthContext);\n",[76,1666,1668,1670,1672,1674,1677,1680,1683,1686,1688,1691],{"class":78,"line":1667},39,[76,1669,210],{"class":89},[76,1671,247],{"class":93},[76,1673,250],{"class":89},[76,1675,1676],{"class":93},"ctx) ",[76,1678,1679],{"class":89},"throw",[76,1681,1682],{"class":89}," new",[76,1684,1685],{"class":138}," Error",[76,1687,687],{"class":93},[76,1689,1690],{"class":100},"'useAuth must be used inside \u003CAuthProvider>'",[76,1692,1255],{"class":93},[76,1694,1696,1698],{"class":78,"line":1695},40,[76,1697,308],{"class":89},[76,1699,1700],{"class":93}," ctx;\n",[76,1702,1704],{"class":78,"line":1703},41,[76,1705,325],{"class":93},[15,1707,1708,1709,1712,1713,1716,1717,1720,1721,1724,1725,1727,1728,1731],{},"Wrap ",[19,1710,1711],{},"\u003CAuthProvider>"," around ",[19,1714,1715],{},"\u003CRouterProvider>"," (or the ",[19,1718,1719],{},"\u003CRoutes>"," tree) at the top of your app so every guard can call ",[19,1722,1723],{},"useAuth()",". The ",[19,1726,161],{}," flag is the key piece — guards block rendering until the ",[19,1729,1730],{},"useEffect"," resolves, preventing a race between \"user is null because we haven't checked yet\" and \"user is null because they're actually logged out.\"",[15,1733,1734,1735,1738,1739,1742,1743,1746],{},"For token storage, keep the ",[31,1736,1737],{},"access token in memory"," (React state) and rely on an ",[31,1740,1741],{},"httpOnly cookie"," for the refresh token. Memory tokens disappear on tab close (no XSS risk from ",[19,1744,1745],{},"localStorage","), and the httpOnly cookie is invisible to JavaScript (no XSS risk at the refresh layer either).",[10,1748,1750],{"id":1749},"client-side-vs-server-side-protection","Client-Side vs. Server-Side Protection",[15,1752,1753,1754,1757,1758,1761],{},"This distinction is the most common follow-up in interviews. ",[31,1755,1756],{},"Client-side route guards are a UX feature, not a security boundary."," They prevent your components from rendering for unauthorized users, which is important for user experience and for not wasting API calls — but a determined attacker can bypass them entirely by calling your API endpoints directly with ",[19,1759,1760],{},"curl"," or a browser devtools fetch.",[15,1763,1764,1767],{},[31,1765,1766],{},"Server-side protection"," is the actual security. Every API endpoint that returns sensitive data must validate the auth token independently:",[67,1769,1771],{"className":69,"code":1770,"language":71,"meta":72,"style":72},"\u002F\u002F Express middleware — real security boundary\nfunction requireAuth(req, res, next) {\n  const token = req.cookies.accessToken;\n  try {\n    req.user = jwt.verify(token, process.env.JWT_SECRET);\n    next();\n  } catch {\n    res.status(401).json({ error: 'Unauthorized' });\n  }\n}\n\n\u002F\u002F Every sensitive endpoint uses the middleware\nrouter.get('\u002Fapi\u002Fdashboard', requireAuth, getDashboardData);\nrouter.get('\u002Fapi\u002Fadmin\u002Fusers', requireAuth, requireRole('admin'), getUsers);\n",[19,1772,1773,1778,1803,1815,1822,1843,1850,1859,1887,1891,1895,1899,1904,1920],{"__ignoreMap":72},[76,1774,1775],{"class":78,"line":79},[76,1776,1777],{"class":82},"\u002F\u002F Express middleware — real security boundary\n",[76,1779,1780,1783,1786,1788,1791,1793,1796,1798,1801],{"class":78,"line":86},[76,1781,1782],{"class":89},"function",[76,1784,1785],{"class":138}," requireAuth",[76,1787,687],{"class":93},[76,1789,1790],{"class":690},"req",[76,1792,158],{"class":93},[76,1794,1795],{"class":690},"res",[76,1797,158],{"class":93},[76,1799,1800],{"class":690},"next",[76,1802,694],{"class":93},[76,1804,1805,1807,1810,1812],{"class":78,"line":107},[76,1806,148],{"class":89},[76,1808,1809],{"class":154}," token",[76,1811,184],{"class":89},[76,1813,1814],{"class":93}," req.cookies.accessToken;\n",[76,1816,1817,1820],{"class":78,"line":122},[76,1818,1819],{"class":89},"  try",[76,1821,1350],{"class":93},[76,1823,1824,1827,1829,1832,1835,1838,1841],{"class":78,"line":129},[76,1825,1826],{"class":93},"    req.user ",[76,1828,167],{"class":89},[76,1830,1831],{"class":93}," jwt.",[76,1833,1834],{"class":138},"verify",[76,1836,1837],{"class":93},"(token, process.env.",[76,1839,1840],{"class":154},"JWT_SECRET",[76,1842,1255],{"class":93},[76,1844,1845,1848],{"class":78,"line":145},[76,1846,1847],{"class":138},"    next",[76,1849,173],{"class":93},[76,1851,1852,1855,1857],{"class":78,"line":176},[76,1853,1854],{"class":93},"  } ",[76,1856,1403],{"class":89},[76,1858,1350],{"class":93},[76,1860,1861,1864,1867,1869,1872,1875,1878,1881,1884],{"class":78,"line":196},[76,1862,1863],{"class":93},"    res.",[76,1865,1866],{"class":138},"status",[76,1868,687],{"class":93},[76,1870,1871],{"class":154},"401",[76,1873,1874],{"class":93},").",[76,1876,1877],{"class":138},"json",[76,1879,1880],{"class":93},"({ error: ",[76,1882,1883],{"class":100},"'Unauthorized'",[76,1885,1886],{"class":93}," });\n",[76,1888,1889],{"class":78,"line":201},[76,1890,297],{"class":93},[76,1892,1893],{"class":78,"line":207},[76,1894,325],{"class":93},[76,1896,1897],{"class":78,"line":237},[76,1898,126],{"emptyLinePlaceholder":125},[76,1900,1901],{"class":78,"line":242},[76,1902,1903],{"class":82},"\u002F\u002F Every sensitive endpoint uses the middleware\n",[76,1905,1906,1909,1912,1914,1917],{"class":78,"line":256},[76,1907,1908],{"class":93},"router.",[76,1910,1911],{"class":138},"get",[76,1913,687],{"class":93},[76,1915,1916],{"class":100},"'\u002Fapi\u002Fdashboard'",[76,1918,1919],{"class":93},", requireAuth, getDashboardData);\n",[76,1921,1922,1924,1926,1928,1931,1934,1937,1939,1941],{"class":78,"line":262},[76,1923,1908],{"class":93},[76,1925,1911],{"class":138},[76,1927,687],{"class":93},[76,1929,1930],{"class":100},"'\u002Fapi\u002Fadmin\u002Fusers'",[76,1932,1933],{"class":93},", requireAuth, ",[76,1935,1936],{"class":138},"requireRole",[76,1938,687],{"class":93},[76,1940,1094],{"class":100},[76,1942,1943],{"class":93},"), getUsers);\n",[15,1945,1946,1947,1950,1951,1953],{},"The mental model: client-side guards protect ",[31,1948,1949],{},"pixels"," (your components), server-side guards protect ",[31,1952,1385],{}," (your API responses). You need both. The client guard prevents a bad user experience; the server guard prevents a data breach.",[10,1955,1957],{"id":1956},"common-interview-questions-at-a-glance","Common Interview Questions at a Glance",[1959,1960,1961,1968,1979,1995,2007,2017,2023,2031],"ul",{},[1962,1963,1964,1967],"li",{},[31,1965,1966],{},"What is a protected route?"," A conditional render — show the page if auth passes, redirect to login if not.",[1962,1969,1970,1978],{},[31,1971,1972,1973,506,1975,1977],{},"Why use ",[19,1974,289],{},[19,1976,60],{},"?"," To overwrite the history entry and prevent a back-button redirect loop.",[1962,1980,1981,1984,1985,1987,1988,1991,1992,1994],{},[31,1982,1983],{},"How do you redirect back after login?"," Pass ",[19,1986,539],{}," in ",[19,1989,1990],{},"state={{ from: location }}"," on the guard's redirect; read ",[19,1993,760],{}," in the login handler.",[1962,1996,1997,2000,2001,2003,2004,2006],{},[31,1998,1999],{},"What's the difference between RequireAuth and RequireRole?"," ",[19,2002,425],{}," checks authentication (logged in?); ",[19,2005,782],{}," checks authorization (right role?). Stack them as nested layout routes.",[1962,2008,2009,2016],{},[31,2010,2011,2012,2015],{},"Why keep ",[19,2013,2014],{},"loading: true"," until auth is resolved?"," To prevent the guard from redirecting a valid user to login during the async session-check on page refresh.",[1962,2018,2019,2022],{},[31,2020,2021],{},"Is client-side protection enough?"," No — it's UX only. Server endpoints must validate tokens independently; client guards can be bypassed with direct API calls.",[1962,2024,2025,2028,2029,533],{},[31,2026,2027],{},"Where should you store access tokens?"," In-memory (React state) to avoid XSS; use an httpOnly cookie for the refresh token so sessions survive page reloads without ",[19,2030,1745],{},[1962,2032,2033,2036,2037,2039,2040,2043,2044,2047],{},[31,2034,2035],{},"How do you test a protected route?"," Render with a mock ",[19,2038,1591],{}," inside ",[19,2041,2042],{},"MemoryRouter",", set ",[19,2045,2046],{},"user: null"," to test the redirect, set a user object to test the protected component renders.",[2049,2050,2051],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":72,"searchDepth":86,"depth":86,"links":2053},[2054,2055,2056,2057,2058,2059,2060],{"id":12,"depth":86,"text":13},{"id":37,"depth":86,"text":38},{"id":515,"depth":86,"text":516},{"id":768,"depth":86,"text":769},{"id":1180,"depth":86,"text":1181},{"id":1749,"depth":86,"text":1750},{"id":1956,"depth":86,"text":1957},"Master React Router v6 protected routes for interviews — RequireAuth wrapper, Navigate redirect, role-based access, auth context hook, and redirect-after-login patterns.","medium","md","React","react",{},"\u002Fblog\u002Freact-protected-routes-guide","\u002Freact\u002Frouting\u002Fprotected-routes",{"title":5,"description":2061},"blog\u002Freact-protected-routes-guide","Protected Routes","Routing","routing","2026-06-24","kN7fsUYaZe4NJ8bgCJo_2PN0gTuVtdx05dMBkGWze-s",1782244083303]