[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-site-config":3,"$fv2cEhwhNTHO78e7J881Wi-XEnQzEGG5wnj1Hud1Y4I0":6,"mdc--exm4ty-key":54},{"gaMeasurementId":4,"bookingUrl":5},"G-7BYTDCVDDR","https:\u002F\u002Ftidycal.com\u002Fcmdcntr\u002F30-minute-meeting",{"post":7,"related":53},{"id":8,"slug":9,"title":10,"description":11,"content":12,"coverImageUrl":13,"coverImageAlt":13,"tags":14,"targetKeywords":20,"demo":21,"featured":25,"tool":13,"status":26,"reviewNote":13,"metaTitle":27,"metaDescription":28,"canonicalUrl":29,"sourceTicketId":13,"agentGenerated":25,"publishedAt":30,"publishedBy":31,"createdAt":30,"updatedAt":32,"authorId":33,"clusterId":34,"ctaId":35,"author":36,"cluster":43,"cta":47},"88e7dad4-88fe-43c0-aaef-af535fff6947","why-ai-generated-authentication-breaks","Why AI-Generated Authentication Always Breaks (And the Fix We Apply Every Time)","AI tools build a convincing login screen and almost never build a real security boundary. Here is why AI-generated auth fails the same way every time, and the server-side fix that closes the hole.","If we get an urgent message about a vibe-coded app, there is a better than even chance it is about authentication. Someone discovered that another user can see their data, or a security researcher emailed them, or a logged-out browser tab loaded a dashboard it had no business loading. The login looked finished. It was not. AI-generated authentication fails the same way nearly every time, and once you see the pattern you can check any AI-built app for it in about ten minutes.\n\n## The pattern: protecting the screen instead of the data\n\nWhen you ask an AI tool to \"add authentication\" or \"make this page require login,\" it builds the part it can see. It creates a login form, stores a session or a token, and conditionally renders the protected UI. Log out, and the dashboard disappears. It looks secure because the thing you were looking at went away.\n\nThe boundary it almost never builds is the one on the server. The API endpoint that feeds that dashboard still answers anyone who calls it directly. The component checked who you are. The endpoint did not.\n\n```typescript\n\u002F\u002F What the AI built: the check is in the UI\nfunction Dashboard() {\n  const { user } = useAuth()\n  if (!user) return \u003CRedirect to=\"\u002Flogin\" \u002F>\n  return \u003COrdersList \u002F>   \u002F\u002F calls GET \u002Fapi\u002Forders\n}\n\n\u002F\u002F The endpoint behind it, as generated: no check at all\nexport async function GET() {\n  const orders = await db.select().from(ordersTable)  \u002F\u002F returns everyone's orders\n  return Response.json(orders)\n}\n```\n\nAnyone who opens the network tab, finds `\u002Fapi\u002Forders`, and requests it without logging in gets the data. The login screen never mattered.\n\n## The second failure: authenticated but not authorized\n\nThe apps that do check for a logged-in user often make the next mistake. They confirm you are someone, then hand back data without checking it is yours. This is the bug that lets user A read user B's records by changing an ID in the URL.\n\n```typescript\n\u002F\u002F Authenticated, but not authorized: any logged-in user can read any order\nexport async function GET(req) {\n  const user = await requireUser(req)        \u002F\u002F good: confirms a valid session\n  const order = await db.query.orders.findFirst({\n    where: eq(orders.id, req.params.id)        \u002F\u002F bad: never checks order.user_id === user.id\n  })\n  return Response.json(order)\n}\n```\n\n\"Are you logged in\" and \"are you allowed to see this specific thing\" are two different questions. AI-generated code routinely answers the first and skips the second.\n\n## Why every AI tool makes this mistake\n\nThis is not a flaw in one builder. It is structural. The model is trained to produce code that satisfies the visible request, and the visible request is a working login experience. UI state is observable and easy to demonstrate. Server-side authorization is invisible when everything goes right, so it is the part that gets dropped. The tool optimizes for the demo, and in the demo, the only user is you.\n\n## The fix: enforce identity and ownership on the server, every time\n\nThe durable fix is a single rule applied without exception: every endpoint that returns or changes user data verifies who is asking and that the data belongs to them, on the server, before it does anything.\n\n```typescript\nexport async function GET(req) {\n  \u002F\u002F 1. Identity: who is this, verified server-side?\n  const user = await requireUser(req)\n\n  \u002F\u002F 2. Ownership: scope the query to this user, do not fetch then check\n  const order = await db.query.orders.findFirst({\n    where: and(\n      eq(orders.id, req.params.id),\n      eq(orders.user_id, user.id),\n    ),\n  })\n\n  \u002F\u002F 3. Fail closed: no row means not found, do not leak existence\n  if (!order) return new Response('Not found', { status: 404 })\n\n  return Response.json(order)\n}\n```\n\nThree things make this work. Identity is checked on the server, not inferred from the UI. Ownership is enforced in the query itself, so you never fetch a record and then decide whether to return it. And the endpoint fails closed: when something is missing or wrong, the default is to deny.\n\nTo audit an existing app, list every API route, open the network tab, and call each protected route while logged out and then while logged in as a different user. Anything that returns data it should not is the hole. In a vibe-coded app you will usually find several, and they cluster exactly where you would expect: the endpoints that were added late, after the login screen was already \"done.\"\n\n## Key takeaways\n\n- AI-generated auth protects the UI and leaves the API open. The login screen is not the boundary.\n- Authentication and authorization are different. Confirm who the user is, then confirm the data is theirs.\n- Scope ownership inside the query and fail closed, so a missing record denies by default instead of leaking.\n- Audit by calling every protected endpoint logged out and as a different user. The holes cluster around recently added routes.\n\nThis is the most common and the most dangerous failure in AI-built software, and it is also one of the most fixable once you know the shape of it. If you are taking an AI-built app to real users, fixing the auth boundary is step one. It is the same first move in our [migration playbook](\u002Fblog\u002Fvibe-coding\u002Fvibe-code-to-production-migration-playbook) and the fix behind the auth item in our [Bolt.new troubleshooting guide](\u002Fblog\u002Fai-builder-guides\u002Ffix-bolt-new-app-6-problems).",null,[15,16,17,18,19],"ai code","authentication","security","debugging","production",[],{"dir":22,"repoUrl":23,"bugBranch":24},"demos\u002Fai-auth-boundary","https:\u002F\u002Fgithub.com\u002Fcommandcenterio\u002Fcmdcntr-demos","bug\u002Fai-auth-boundary",false,"published","Why AI-Generated Authentication Always Breaks","AI-generated auth protects the UI, not the data. Here is the exact failure pattern, why every AI tool makes it, and the server-side fix that actually secures your app.","https:\u002F\u002Fcmdcntr.io\u002Fblog\u002Ffix-ai-code\u002Fwhy-ai-generated-authentication-breaks","2026-07-04T15:08:41.588Z","7452d3cc-b0aa-46e4-96e6-da9c0225c471","2026-07-04T16:23:59.881Z","ed80da88-f3d1-4aa9-9373-18c39fecb740","d677fc7f-42a6-4beb-9bf1-8cfff2907505","c0fcf6a1-58d2-4262-8996-3ad29d58eea5",{"slug":37,"name":38,"avatarUrl":39,"role":40,"bio":41,"isPublic":42},"michael-graham","Michael Graham","\u002Fapi\u002Fpublic\u002Favatar\u002F7452d3cc-b0aa-46e4-96e6-da9c0225c471","Founder & Software Engineer","Obsessed with building top-tier web software and crafting unique, polished user experiences.",true,{"slug":44,"name":45,"color":46},"fix-ai-code","Fix AI Code","#3B82F6",{"id":35,"name":48,"ctaType":49,"heading":50,"description":51,"buttonText":52,"url":5,"leadMagnetUrl":13},"Book a Call (blog)","book_call","Shipping AI-generated code that keeps breaking?","Book a free 30-minute call with a senior engineer. We diagnose what is going wrong and give you a concrete fix plan, no obligation.","Book a free call",[],{"data":55,"body":56},{},{"type":57,"children":58},"root",[59,67,74,79,84,419,432,438,443,726,731,737,742,748,753,1207,1212,1217,1223,1248,1269],{"type":60,"tag":61,"props":62,"children":63},"element","p",{},[64],{"type":65,"value":66},"text","If we get an urgent message about a vibe-coded app, there is a better than even chance it is about authentication. Someone discovered that another user can see their data, or a security researcher emailed them, or a logged-out browser tab loaded a dashboard it had no business loading. The login looked finished. It was not. AI-generated authentication fails the same way nearly every time, and once you see the pattern you can check any AI-built app for it in about ten minutes.",{"type":60,"tag":68,"props":69,"children":71},"h2",{"id":70},"the-pattern-protecting-the-screen-instead-of-the-data",[72],{"type":65,"value":73},"The pattern: protecting the screen instead of the data",{"type":60,"tag":61,"props":75,"children":76},{},[77],{"type":65,"value":78},"When you ask an AI tool to \"add authentication\" or \"make this page require login,\" it builds the part it can see. It creates a login form, stores a session or a token, and conditionally renders the protected UI. Log out, and the dashboard disappears. It looks secure because the thing you were looking at went away.",{"type":60,"tag":61,"props":80,"children":81},{},[82],{"type":65,"value":83},"The boundary it almost never builds is the one on the server. The API endpoint that feeds that dashboard still answers anyone who calls it directly. The component checked who you are. The endpoint did not.",{"type":60,"tag":85,"props":86,"children":91},"pre",{"className":87,"code":88,"language":89,"meta":90,"style":90},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F What the AI built: the check is in the UI\nfunction Dashboard() {\n  const { user } = useAuth()\n  if (!user) return \u003CRedirect to=\"\u002Flogin\" \u002F>\n  return \u003COrdersList \u002F>   \u002F\u002F calls GET \u002Fapi\u002Forders\n}\n\n\u002F\u002F The endpoint behind it, as generated: no check at all\nexport async function GET() {\n  const orders = await db.select().from(ordersTable)  \u002F\u002F returns everyone's orders\n  return Response.json(orders)\n}\n","typescript","",[92],{"type":60,"tag":93,"props":94,"children":95},"code",{"__ignoreMap":90},[96,108,135,176,257,290,299,308,317,331,402,411],{"type":60,"tag":97,"props":98,"children":101},"span",{"class":99,"line":100},"line",1,[102],{"type":60,"tag":97,"props":103,"children":105},{"style":104},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[106],{"type":65,"value":107},"\u002F\u002F What the AI built: the check is in the UI\n",{"type":60,"tag":97,"props":109,"children":111},{"class":99,"line":110},2,[112,118,124,130],{"type":60,"tag":97,"props":113,"children":115},{"style":114},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[116],{"type":65,"value":117},"function",{"type":60,"tag":97,"props":119,"children":121},{"style":120},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[122],{"type":65,"value":123}," Dashboard",{"type":60,"tag":97,"props":125,"children":127},{"style":126},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[128],{"type":65,"value":129},"()",{"type":60,"tag":97,"props":131,"children":132},{"style":126},[133],{"type":65,"value":134}," {\n",{"type":60,"tag":97,"props":136,"children":138},{"class":99,"line":137},3,[139,144,149,155,160,165,170],{"type":60,"tag":97,"props":140,"children":141},{"style":114},[142],{"type":65,"value":143},"  const",{"type":60,"tag":97,"props":145,"children":146},{"style":126},[147],{"type":65,"value":148}," {",{"type":60,"tag":97,"props":150,"children":152},{"style":151},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[153],{"type":65,"value":154}," user",{"type":60,"tag":97,"props":156,"children":157},{"style":126},[158],{"type":65,"value":159}," }",{"type":60,"tag":97,"props":161,"children":162},{"style":126},[163],{"type":65,"value":164}," =",{"type":60,"tag":97,"props":166,"children":167},{"style":120},[168],{"type":65,"value":169}," useAuth",{"type":60,"tag":97,"props":171,"children":173},{"style":172},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[174],{"type":65,"value":175},"()\n",{"type":60,"tag":97,"props":177,"children":179},{"class":99,"line":178},4,[180,186,191,196,201,206,211,216,222,227,232,237,243,247,252],{"type":60,"tag":97,"props":181,"children":183},{"style":182},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[184],{"type":65,"value":185},"  if",{"type":60,"tag":97,"props":187,"children":188},{"style":172},[189],{"type":65,"value":190}," (",{"type":60,"tag":97,"props":192,"children":193},{"style":126},[194],{"type":65,"value":195},"!",{"type":60,"tag":97,"props":197,"children":198},{"style":151},[199],{"type":65,"value":200},"user",{"type":60,"tag":97,"props":202,"children":203},{"style":172},[204],{"type":65,"value":205},") ",{"type":60,"tag":97,"props":207,"children":208},{"style":182},[209],{"type":65,"value":210},"return",{"type":60,"tag":97,"props":212,"children":213},{"style":126},[214],{"type":65,"value":215}," \u003C",{"type":60,"tag":97,"props":217,"children":219},{"style":218},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[220],{"type":65,"value":221},"Redirect",{"type":60,"tag":97,"props":223,"children":224},{"style":218},[225],{"type":65,"value":226}," to",{"type":60,"tag":97,"props":228,"children":229},{"style":126},[230],{"type":65,"value":231},"=",{"type":60,"tag":97,"props":233,"children":234},{"style":126},[235],{"type":65,"value":236},"\"",{"type":60,"tag":97,"props":238,"children":240},{"style":239},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[241],{"type":65,"value":242},"\u002Flogin",{"type":60,"tag":97,"props":244,"children":245},{"style":126},[246],{"type":65,"value":236},{"type":60,"tag":97,"props":248,"children":249},{"style":172},[250],{"type":65,"value":251}," \u002F",{"type":60,"tag":97,"props":253,"children":254},{"style":126},[255],{"type":65,"value":256},">\n",{"type":60,"tag":97,"props":258,"children":260},{"class":99,"line":259},5,[261,266,271,276,280,285],{"type":60,"tag":97,"props":262,"children":263},{"style":172},[264],{"type":65,"value":265},"  return ",{"type":60,"tag":97,"props":267,"children":268},{"style":126},[269],{"type":65,"value":270},"\u003C",{"type":60,"tag":97,"props":272,"children":273},{"style":218},[274],{"type":65,"value":275},"OrdersList",{"type":60,"tag":97,"props":277,"children":278},{"style":172},[279],{"type":65,"value":251},{"type":60,"tag":97,"props":281,"children":282},{"style":126},[283],{"type":65,"value":284},">",{"type":60,"tag":97,"props":286,"children":287},{"style":172},[288],{"type":65,"value":289},"   \u002F\u002F calls GET \u002Fapi\u002Forders\n",{"type":60,"tag":97,"props":291,"children":293},{"class":99,"line":292},6,[294],{"type":60,"tag":97,"props":295,"children":296},{"style":172},[297],{"type":65,"value":298},"}\n",{"type":60,"tag":97,"props":300,"children":302},{"class":99,"line":301},7,[303],{"type":60,"tag":97,"props":304,"children":305},{"emptyLinePlaceholder":42},[306],{"type":65,"value":307},"\n",{"type":60,"tag":97,"props":309,"children":311},{"class":99,"line":310},8,[312],{"type":60,"tag":97,"props":313,"children":314},{"style":172},[315],{"type":65,"value":316},"\u002F\u002F The endpoint behind it, as generated: no check at all\n",{"type":60,"tag":97,"props":318,"children":320},{"class":99,"line":319},9,[321,326],{"type":60,"tag":97,"props":322,"children":323},{"style":172},[324],{"type":65,"value":325},"export async function GET() ",{"type":60,"tag":97,"props":327,"children":328},{"style":126},[329],{"type":65,"value":330},"{\n",{"type":60,"tag":97,"props":332,"children":334},{"class":99,"line":333},10,[335,340,345,349,354,359,364,369,373,377,382,387,392,397],{"type":60,"tag":97,"props":336,"children":337},{"style":172},[338],{"type":65,"value":339},"  const ",{"type":60,"tag":97,"props":341,"children":342},{"style":151},[343],{"type":65,"value":344},"orders",{"type":60,"tag":97,"props":346,"children":347},{"style":126},[348],{"type":65,"value":164},{"type":60,"tag":97,"props":350,"children":351},{"style":182},[352],{"type":65,"value":353}," await",{"type":60,"tag":97,"props":355,"children":356},{"style":151},[357],{"type":65,"value":358}," db",{"type":60,"tag":97,"props":360,"children":361},{"style":126},[362],{"type":65,"value":363},".",{"type":60,"tag":97,"props":365,"children":366},{"style":120},[367],{"type":65,"value":368},"select",{"type":60,"tag":97,"props":370,"children":371},{"style":172},[372],{"type":65,"value":129},{"type":60,"tag":97,"props":374,"children":375},{"style":126},[376],{"type":65,"value":363},{"type":60,"tag":97,"props":378,"children":379},{"style":120},[380],{"type":65,"value":381},"from",{"type":60,"tag":97,"props":383,"children":384},{"style":172},[385],{"type":65,"value":386},"(",{"type":60,"tag":97,"props":388,"children":389},{"style":151},[390],{"type":65,"value":391},"ordersTable",{"type":60,"tag":97,"props":393,"children":394},{"style":172},[395],{"type":65,"value":396},")  ",{"type":60,"tag":97,"props":398,"children":399},{"style":104},[400],{"type":65,"value":401},"\u002F\u002F returns everyone's orders\n",{"type":60,"tag":97,"props":403,"children":405},{"class":99,"line":404},11,[406],{"type":60,"tag":97,"props":407,"children":408},{"style":172},[409],{"type":65,"value":410},"  return Response.json(orders)\n",{"type":60,"tag":97,"props":412,"children":414},{"class":99,"line":413},12,[415],{"type":60,"tag":97,"props":416,"children":417},{"style":126},[418],{"type":65,"value":298},{"type":60,"tag":61,"props":420,"children":421},{},[422,424,430],{"type":65,"value":423},"Anyone who opens the network tab, finds ",{"type":60,"tag":93,"props":425,"children":427},{"className":426},[],[428],{"type":65,"value":429},"\u002Fapi\u002Forders",{"type":65,"value":431},", and requests it without logging in gets the data. The login screen never mattered.",{"type":60,"tag":68,"props":433,"children":435},{"id":434},"the-second-failure-authenticated-but-not-authorized",[436],{"type":65,"value":437},"The second failure: authenticated but not authorized",{"type":60,"tag":61,"props":439,"children":440},{},[441],{"type":65,"value":442},"The apps that do check for a logged-in user often make the next mistake. They confirm you are someone, then hand back data without checking it is yours. This is the bug that lets user A read user B's records by changing an ID in the URL.",{"type":60,"tag":85,"props":444,"children":446},{"className":87,"code":445,"language":89,"meta":90,"style":90},"\u002F\u002F Authenticated, but not authorized: any logged-in user can read any order\nexport async function GET(req) {\n  const user = await requireUser(req)        \u002F\u002F good: confirms a valid session\n  const order = await db.query.orders.findFirst({\n    where: eq(orders.id, req.params.id)        \u002F\u002F bad: never checks order.user_id === user.id\n  })\n  return Response.json(order)\n}\n",[447],{"type":60,"tag":93,"props":448,"children":449},{"__ignoreMap":90},[450,458,500,542,600,671,684,719],{"type":60,"tag":97,"props":451,"children":452},{"class":99,"line":100},[453],{"type":60,"tag":97,"props":454,"children":455},{"style":104},[456],{"type":65,"value":457},"\u002F\u002F Authenticated, but not authorized: any logged-in user can read any order\n",{"type":60,"tag":97,"props":459,"children":460},{"class":99,"line":110},[461,466,471,476,481,485,491,496],{"type":60,"tag":97,"props":462,"children":463},{"style":182},[464],{"type":65,"value":465},"export",{"type":60,"tag":97,"props":467,"children":468},{"style":114},[469],{"type":65,"value":470}," async",{"type":60,"tag":97,"props":472,"children":473},{"style":114},[474],{"type":65,"value":475}," function",{"type":60,"tag":97,"props":477,"children":478},{"style":120},[479],{"type":65,"value":480}," GET",{"type":60,"tag":97,"props":482,"children":483},{"style":126},[484],{"type":65,"value":386},{"type":60,"tag":97,"props":486,"children":488},{"style":487},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[489],{"type":65,"value":490},"req",{"type":60,"tag":97,"props":492,"children":493},{"style":126},[494],{"type":65,"value":495},")",{"type":60,"tag":97,"props":497,"children":498},{"style":126},[499],{"type":65,"value":134},{"type":60,"tag":97,"props":501,"children":502},{"class":99,"line":137},[503,507,511,515,519,524,528,532,537],{"type":60,"tag":97,"props":504,"children":505},{"style":114},[506],{"type":65,"value":143},{"type":60,"tag":97,"props":508,"children":509},{"style":151},[510],{"type":65,"value":154},{"type":60,"tag":97,"props":512,"children":513},{"style":126},[514],{"type":65,"value":164},{"type":60,"tag":97,"props":516,"children":517},{"style":182},[518],{"type":65,"value":353},{"type":60,"tag":97,"props":520,"children":521},{"style":120},[522],{"type":65,"value":523}," requireUser",{"type":60,"tag":97,"props":525,"children":526},{"style":172},[527],{"type":65,"value":386},{"type":60,"tag":97,"props":529,"children":530},{"style":151},[531],{"type":65,"value":490},{"type":60,"tag":97,"props":533,"children":534},{"style":172},[535],{"type":65,"value":536},")        ",{"type":60,"tag":97,"props":538,"children":539},{"style":104},[540],{"type":65,"value":541},"\u002F\u002F good: confirms a valid session\n",{"type":60,"tag":97,"props":543,"children":544},{"class":99,"line":178},[545,549,554,558,562,566,570,575,579,583,587,592,596],{"type":60,"tag":97,"props":546,"children":547},{"style":114},[548],{"type":65,"value":143},{"type":60,"tag":97,"props":550,"children":551},{"style":151},[552],{"type":65,"value":553}," order",{"type":60,"tag":97,"props":555,"children":556},{"style":126},[557],{"type":65,"value":164},{"type":60,"tag":97,"props":559,"children":560},{"style":182},[561],{"type":65,"value":353},{"type":60,"tag":97,"props":563,"children":564},{"style":151},[565],{"type":65,"value":358},{"type":60,"tag":97,"props":567,"children":568},{"style":126},[569],{"type":65,"value":363},{"type":60,"tag":97,"props":571,"children":572},{"style":151},[573],{"type":65,"value":574},"query",{"type":60,"tag":97,"props":576,"children":577},{"style":126},[578],{"type":65,"value":363},{"type":60,"tag":97,"props":580,"children":581},{"style":151},[582],{"type":65,"value":344},{"type":60,"tag":97,"props":584,"children":585},{"style":126},[586],{"type":65,"value":363},{"type":60,"tag":97,"props":588,"children":589},{"style":120},[590],{"type":65,"value":591},"findFirst",{"type":60,"tag":97,"props":593,"children":594},{"style":172},[595],{"type":65,"value":386},{"type":60,"tag":97,"props":597,"children":598},{"style":126},[599],{"type":65,"value":330},{"type":60,"tag":97,"props":601,"children":602},{"class":99,"line":259},[603,608,613,618,622,626,630,635,640,645,649,654,658,662,666],{"type":60,"tag":97,"props":604,"children":605},{"style":172},[606],{"type":65,"value":607},"    where",{"type":60,"tag":97,"props":609,"children":610},{"style":126},[611],{"type":65,"value":612},":",{"type":60,"tag":97,"props":614,"children":615},{"style":120},[616],{"type":65,"value":617}," eq",{"type":60,"tag":97,"props":619,"children":620},{"style":172},[621],{"type":65,"value":386},{"type":60,"tag":97,"props":623,"children":624},{"style":151},[625],{"type":65,"value":344},{"type":60,"tag":97,"props":627,"children":628},{"style":126},[629],{"type":65,"value":363},{"type":60,"tag":97,"props":631,"children":632},{"style":151},[633],{"type":65,"value":634},"id",{"type":60,"tag":97,"props":636,"children":637},{"style":126},[638],{"type":65,"value":639},",",{"type":60,"tag":97,"props":641,"children":642},{"style":151},[643],{"type":65,"value":644}," req",{"type":60,"tag":97,"props":646,"children":647},{"style":126},[648],{"type":65,"value":363},{"type":60,"tag":97,"props":650,"children":651},{"style":151},[652],{"type":65,"value":653},"params",{"type":60,"tag":97,"props":655,"children":656},{"style":126},[657],{"type":65,"value":363},{"type":60,"tag":97,"props":659,"children":660},{"style":151},[661],{"type":65,"value":634},{"type":60,"tag":97,"props":663,"children":664},{"style":172},[665],{"type":65,"value":536},{"type":60,"tag":97,"props":667,"children":668},{"style":104},[669],{"type":65,"value":670},"\u002F\u002F bad: never checks order.user_id === user.id\n",{"type":60,"tag":97,"props":672,"children":673},{"class":99,"line":292},[674,679],{"type":60,"tag":97,"props":675,"children":676},{"style":126},[677],{"type":65,"value":678},"  }",{"type":60,"tag":97,"props":680,"children":681},{"style":172},[682],{"type":65,"value":683},")\n",{"type":60,"tag":97,"props":685,"children":686},{"class":99,"line":301},[687,692,697,701,706,710,715],{"type":60,"tag":97,"props":688,"children":689},{"style":182},[690],{"type":65,"value":691},"  return",{"type":60,"tag":97,"props":693,"children":694},{"style":151},[695],{"type":65,"value":696}," Response",{"type":60,"tag":97,"props":698,"children":699},{"style":126},[700],{"type":65,"value":363},{"type":60,"tag":97,"props":702,"children":703},{"style":120},[704],{"type":65,"value":705},"json",{"type":60,"tag":97,"props":707,"children":708},{"style":172},[709],{"type":65,"value":386},{"type":60,"tag":97,"props":711,"children":712},{"style":151},[713],{"type":65,"value":714},"order",{"type":60,"tag":97,"props":716,"children":717},{"style":172},[718],{"type":65,"value":683},{"type":60,"tag":97,"props":720,"children":721},{"class":99,"line":310},[722],{"type":60,"tag":97,"props":723,"children":724},{"style":126},[725],{"type":65,"value":298},{"type":60,"tag":61,"props":727,"children":728},{},[729],{"type":65,"value":730},"\"Are you logged in\" and \"are you allowed to see this specific thing\" are two different questions. AI-generated code routinely answers the first and skips the second.",{"type":60,"tag":68,"props":732,"children":734},{"id":733},"why-every-ai-tool-makes-this-mistake",[735],{"type":65,"value":736},"Why every AI tool makes this mistake",{"type":60,"tag":61,"props":738,"children":739},{},[740],{"type":65,"value":741},"This is not a flaw in one builder. It is structural. The model is trained to produce code that satisfies the visible request, and the visible request is a working login experience. UI state is observable and easy to demonstrate. Server-side authorization is invisible when everything goes right, so it is the part that gets dropped. The tool optimizes for the demo, and in the demo, the only user is you.",{"type":60,"tag":68,"props":743,"children":745},{"id":744},"the-fix-enforce-identity-and-ownership-on-the-server-every-time",[746],{"type":65,"value":747},"The fix: enforce identity and ownership on the server, every time",{"type":60,"tag":61,"props":749,"children":750},{},[751],{"type":65,"value":752},"The durable fix is a single rule applied without exception: every endpoint that returns or changes user data verifies who is asking and that the data belongs to them, on the server, before it does anything.",{"type":60,"tag":85,"props":754,"children":756},{"className":87,"code":755,"language":89,"meta":90,"style":90},"export async function GET(req) {\n  \u002F\u002F 1. Identity: who is this, verified server-side?\n  const user = await requireUser(req)\n\n  \u002F\u002F 2. Ownership: scope the query to this user, do not fetch then check\n  const order = await db.query.orders.findFirst({\n    where: and(\n      eq(orders.id, req.params.id),\n      eq(orders.user_id, user.id),\n    ),\n  })\n\n  \u002F\u002F 3. Fail closed: no row means not found, do not leak existence\n  if (!order) return new Response('Not found', { status: 404 })\n\n  return Response.json(order)\n}\n",[757],{"type":60,"tag":93,"props":758,"children":759},{"__ignoreMap":90},[760,795,803,838,845,853,908,929,986,1034,1046,1057,1064,1073,1159,1167,1199],{"type":60,"tag":97,"props":761,"children":762},{"class":99,"line":100},[763,767,771,775,779,783,787,791],{"type":60,"tag":97,"props":764,"children":765},{"style":182},[766],{"type":65,"value":465},{"type":60,"tag":97,"props":768,"children":769},{"style":114},[770],{"type":65,"value":470},{"type":60,"tag":97,"props":772,"children":773},{"style":114},[774],{"type":65,"value":475},{"type":60,"tag":97,"props":776,"children":777},{"style":120},[778],{"type":65,"value":480},{"type":60,"tag":97,"props":780,"children":781},{"style":126},[782],{"type":65,"value":386},{"type":60,"tag":97,"props":784,"children":785},{"style":487},[786],{"type":65,"value":490},{"type":60,"tag":97,"props":788,"children":789},{"style":126},[790],{"type":65,"value":495},{"type":60,"tag":97,"props":792,"children":793},{"style":126},[794],{"type":65,"value":134},{"type":60,"tag":97,"props":796,"children":797},{"class":99,"line":110},[798],{"type":60,"tag":97,"props":799,"children":800},{"style":104},[801],{"type":65,"value":802},"  \u002F\u002F 1. Identity: who is this, verified server-side?\n",{"type":60,"tag":97,"props":804,"children":805},{"class":99,"line":137},[806,810,814,818,822,826,830,834],{"type":60,"tag":97,"props":807,"children":808},{"style":114},[809],{"type":65,"value":143},{"type":60,"tag":97,"props":811,"children":812},{"style":151},[813],{"type":65,"value":154},{"type":60,"tag":97,"props":815,"children":816},{"style":126},[817],{"type":65,"value":164},{"type":60,"tag":97,"props":819,"children":820},{"style":182},[821],{"type":65,"value":353},{"type":60,"tag":97,"props":823,"children":824},{"style":120},[825],{"type":65,"value":523},{"type":60,"tag":97,"props":827,"children":828},{"style":172},[829],{"type":65,"value":386},{"type":60,"tag":97,"props":831,"children":832},{"style":151},[833],{"type":65,"value":490},{"type":60,"tag":97,"props":835,"children":836},{"style":172},[837],{"type":65,"value":683},{"type":60,"tag":97,"props":839,"children":840},{"class":99,"line":178},[841],{"type":60,"tag":97,"props":842,"children":843},{"emptyLinePlaceholder":42},[844],{"type":65,"value":307},{"type":60,"tag":97,"props":846,"children":847},{"class":99,"line":259},[848],{"type":60,"tag":97,"props":849,"children":850},{"style":104},[851],{"type":65,"value":852},"  \u002F\u002F 2. Ownership: scope the query to this user, do not fetch then check\n",{"type":60,"tag":97,"props":854,"children":855},{"class":99,"line":292},[856,860,864,868,872,876,880,884,888,892,896,900,904],{"type":60,"tag":97,"props":857,"children":858},{"style":114},[859],{"type":65,"value":143},{"type":60,"tag":97,"props":861,"children":862},{"style":151},[863],{"type":65,"value":553},{"type":60,"tag":97,"props":865,"children":866},{"style":126},[867],{"type":65,"value":164},{"type":60,"tag":97,"props":869,"children":870},{"style":182},[871],{"type":65,"value":353},{"type":60,"tag":97,"props":873,"children":874},{"style":151},[875],{"type":65,"value":358},{"type":60,"tag":97,"props":877,"children":878},{"style":126},[879],{"type":65,"value":363},{"type":60,"tag":97,"props":881,"children":882},{"style":151},[883],{"type":65,"value":574},{"type":60,"tag":97,"props":885,"children":886},{"style":126},[887],{"type":65,"value":363},{"type":60,"tag":97,"props":889,"children":890},{"style":151},[891],{"type":65,"value":344},{"type":60,"tag":97,"props":893,"children":894},{"style":126},[895],{"type":65,"value":363},{"type":60,"tag":97,"props":897,"children":898},{"style":120},[899],{"type":65,"value":591},{"type":60,"tag":97,"props":901,"children":902},{"style":172},[903],{"type":65,"value":386},{"type":60,"tag":97,"props":905,"children":906},{"style":126},[907],{"type":65,"value":330},{"type":60,"tag":97,"props":909,"children":910},{"class":99,"line":301},[911,915,919,924],{"type":60,"tag":97,"props":912,"children":913},{"style":172},[914],{"type":65,"value":607},{"type":60,"tag":97,"props":916,"children":917},{"style":126},[918],{"type":65,"value":612},{"type":60,"tag":97,"props":920,"children":921},{"style":120},[922],{"type":65,"value":923}," and",{"type":60,"tag":97,"props":925,"children":926},{"style":172},[927],{"type":65,"value":928},"(\n",{"type":60,"tag":97,"props":930,"children":931},{"class":99,"line":310},[932,937,941,945,949,953,957,961,965,969,973,977,981],{"type":60,"tag":97,"props":933,"children":934},{"style":120},[935],{"type":65,"value":936},"      eq",{"type":60,"tag":97,"props":938,"children":939},{"style":172},[940],{"type":65,"value":386},{"type":60,"tag":97,"props":942,"children":943},{"style":151},[944],{"type":65,"value":344},{"type":60,"tag":97,"props":946,"children":947},{"style":126},[948],{"type":65,"value":363},{"type":60,"tag":97,"props":950,"children":951},{"style":151},[952],{"type":65,"value":634},{"type":60,"tag":97,"props":954,"children":955},{"style":126},[956],{"type":65,"value":639},{"type":60,"tag":97,"props":958,"children":959},{"style":151},[960],{"type":65,"value":644},{"type":60,"tag":97,"props":962,"children":963},{"style":126},[964],{"type":65,"value":363},{"type":60,"tag":97,"props":966,"children":967},{"style":151},[968],{"type":65,"value":653},{"type":60,"tag":97,"props":970,"children":971},{"style":126},[972],{"type":65,"value":363},{"type":60,"tag":97,"props":974,"children":975},{"style":151},[976],{"type":65,"value":634},{"type":60,"tag":97,"props":978,"children":979},{"style":172},[980],{"type":65,"value":495},{"type":60,"tag":97,"props":982,"children":983},{"style":126},[984],{"type":65,"value":985},",\n",{"type":60,"tag":97,"props":987,"children":988},{"class":99,"line":319},[989,993,997,1001,1005,1010,1014,1018,1022,1026,1030],{"type":60,"tag":97,"props":990,"children":991},{"style":120},[992],{"type":65,"value":936},{"type":60,"tag":97,"props":994,"children":995},{"style":172},[996],{"type":65,"value":386},{"type":60,"tag":97,"props":998,"children":999},{"style":151},[1000],{"type":65,"value":344},{"type":60,"tag":97,"props":1002,"children":1003},{"style":126},[1004],{"type":65,"value":363},{"type":60,"tag":97,"props":1006,"children":1007},{"style":151},[1008],{"type":65,"value":1009},"user_id",{"type":60,"tag":97,"props":1011,"children":1012},{"style":126},[1013],{"type":65,"value":639},{"type":60,"tag":97,"props":1015,"children":1016},{"style":151},[1017],{"type":65,"value":154},{"type":60,"tag":97,"props":1019,"children":1020},{"style":126},[1021],{"type":65,"value":363},{"type":60,"tag":97,"props":1023,"children":1024},{"style":151},[1025],{"type":65,"value":634},{"type":60,"tag":97,"props":1027,"children":1028},{"style":172},[1029],{"type":65,"value":495},{"type":60,"tag":97,"props":1031,"children":1032},{"style":126},[1033],{"type":65,"value":985},{"type":60,"tag":97,"props":1035,"children":1036},{"class":99,"line":333},[1037,1042],{"type":60,"tag":97,"props":1038,"children":1039},{"style":172},[1040],{"type":65,"value":1041},"    )",{"type":60,"tag":97,"props":1043,"children":1044},{"style":126},[1045],{"type":65,"value":985},{"type":60,"tag":97,"props":1047,"children":1048},{"class":99,"line":404},[1049,1053],{"type":60,"tag":97,"props":1050,"children":1051},{"style":126},[1052],{"type":65,"value":678},{"type":60,"tag":97,"props":1054,"children":1055},{"style":172},[1056],{"type":65,"value":683},{"type":60,"tag":97,"props":1058,"children":1059},{"class":99,"line":413},[1060],{"type":60,"tag":97,"props":1061,"children":1062},{"emptyLinePlaceholder":42},[1063],{"type":65,"value":307},{"type":60,"tag":97,"props":1065,"children":1067},{"class":99,"line":1066},13,[1068],{"type":60,"tag":97,"props":1069,"children":1070},{"style":104},[1071],{"type":65,"value":1072},"  \u002F\u002F 3. Fail closed: no row means not found, do not leak existence\n",{"type":60,"tag":97,"props":1074,"children":1076},{"class":99,"line":1075},14,[1077,1081,1085,1089,1093,1097,1101,1106,1110,1114,1119,1124,1128,1132,1136,1141,1145,1151,1155],{"type":60,"tag":97,"props":1078,"children":1079},{"style":182},[1080],{"type":65,"value":185},{"type":60,"tag":97,"props":1082,"children":1083},{"style":172},[1084],{"type":65,"value":190},{"type":60,"tag":97,"props":1086,"children":1087},{"style":126},[1088],{"type":65,"value":195},{"type":60,"tag":97,"props":1090,"children":1091},{"style":151},[1092],{"type":65,"value":714},{"type":60,"tag":97,"props":1094,"children":1095},{"style":172},[1096],{"type":65,"value":205},{"type":60,"tag":97,"props":1098,"children":1099},{"style":182},[1100],{"type":65,"value":210},{"type":60,"tag":97,"props":1102,"children":1103},{"style":126},[1104],{"type":65,"value":1105}," new",{"type":60,"tag":97,"props":1107,"children":1108},{"style":120},[1109],{"type":65,"value":696},{"type":60,"tag":97,"props":1111,"children":1112},{"style":172},[1113],{"type":65,"value":386},{"type":60,"tag":97,"props":1115,"children":1116},{"style":126},[1117],{"type":65,"value":1118},"'",{"type":60,"tag":97,"props":1120,"children":1121},{"style":239},[1122],{"type":65,"value":1123},"Not found",{"type":60,"tag":97,"props":1125,"children":1126},{"style":126},[1127],{"type":65,"value":1118},{"type":60,"tag":97,"props":1129,"children":1130},{"style":126},[1131],{"type":65,"value":639},{"type":60,"tag":97,"props":1133,"children":1134},{"style":126},[1135],{"type":65,"value":148},{"type":60,"tag":97,"props":1137,"children":1138},{"style":172},[1139],{"type":65,"value":1140}," status",{"type":60,"tag":97,"props":1142,"children":1143},{"style":126},[1144],{"type":65,"value":612},{"type":60,"tag":97,"props":1146,"children":1148},{"style":1147},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[1149],{"type":65,"value":1150}," 404",{"type":60,"tag":97,"props":1152,"children":1153},{"style":126},[1154],{"type":65,"value":159},{"type":60,"tag":97,"props":1156,"children":1157},{"style":172},[1158],{"type":65,"value":683},{"type":60,"tag":97,"props":1160,"children":1162},{"class":99,"line":1161},15,[1163],{"type":60,"tag":97,"props":1164,"children":1165},{"emptyLinePlaceholder":42},[1166],{"type":65,"value":307},{"type":60,"tag":97,"props":1168,"children":1170},{"class":99,"line":1169},16,[1171,1175,1179,1183,1187,1191,1195],{"type":60,"tag":97,"props":1172,"children":1173},{"style":182},[1174],{"type":65,"value":691},{"type":60,"tag":97,"props":1176,"children":1177},{"style":151},[1178],{"type":65,"value":696},{"type":60,"tag":97,"props":1180,"children":1181},{"style":126},[1182],{"type":65,"value":363},{"type":60,"tag":97,"props":1184,"children":1185},{"style":120},[1186],{"type":65,"value":705},{"type":60,"tag":97,"props":1188,"children":1189},{"style":172},[1190],{"type":65,"value":386},{"type":60,"tag":97,"props":1192,"children":1193},{"style":151},[1194],{"type":65,"value":714},{"type":60,"tag":97,"props":1196,"children":1197},{"style":172},[1198],{"type":65,"value":683},{"type":60,"tag":97,"props":1200,"children":1202},{"class":99,"line":1201},17,[1203],{"type":60,"tag":97,"props":1204,"children":1205},{"style":126},[1206],{"type":65,"value":298},{"type":60,"tag":61,"props":1208,"children":1209},{},[1210],{"type":65,"value":1211},"Three things make this work. Identity is checked on the server, not inferred from the UI. Ownership is enforced in the query itself, so you never fetch a record and then decide whether to return it. And the endpoint fails closed: when something is missing or wrong, the default is to deny.",{"type":60,"tag":61,"props":1213,"children":1214},{},[1215],{"type":65,"value":1216},"To audit an existing app, list every API route, open the network tab, and call each protected route while logged out and then while logged in as a different user. Anything that returns data it should not is the hole. In a vibe-coded app you will usually find several, and they cluster exactly where you would expect: the endpoints that were added late, after the login screen was already \"done.\"",{"type":60,"tag":68,"props":1218,"children":1220},{"id":1219},"key-takeaways",[1221],{"type":65,"value":1222},"Key takeaways",{"type":60,"tag":1224,"props":1225,"children":1226},"ul",{},[1227,1233,1238,1243],{"type":60,"tag":1228,"props":1229,"children":1230},"li",{},[1231],{"type":65,"value":1232},"AI-generated auth protects the UI and leaves the API open. The login screen is not the boundary.",{"type":60,"tag":1228,"props":1234,"children":1235},{},[1236],{"type":65,"value":1237},"Authentication and authorization are different. Confirm who the user is, then confirm the data is theirs.",{"type":60,"tag":1228,"props":1239,"children":1240},{},[1241],{"type":65,"value":1242},"Scope ownership inside the query and fail closed, so a missing record denies by default instead of leaking.",{"type":60,"tag":1228,"props":1244,"children":1245},{},[1246],{"type":65,"value":1247},"Audit by calling every protected endpoint logged out and as a different user. The holes cluster around recently added routes.",{"type":60,"tag":61,"props":1249,"children":1250},{},[1251,1253,1260,1262,1268],{"type":65,"value":1252},"This is the most common and the most dangerous failure in AI-built software, and it is also one of the most fixable once you know the shape of it. If you are taking an AI-built app to real users, fixing the auth boundary is step one. It is the same first move in our ",{"type":60,"tag":1254,"props":1255,"children":1257},"a",{"href":1256},"\u002Fblog\u002Fvibe-coding\u002Fvibe-code-to-production-migration-playbook",[1258],{"type":65,"value":1259},"migration playbook",{"type":65,"value":1261}," and the fix behind the auth item in our ",{"type":60,"tag":1254,"props":1263,"children":1265},{"href":1264},"\u002Fblog\u002Fai-builder-guides\u002Ffix-bolt-new-app-6-problems",[1266],{"type":65,"value":1267},"Bolt.new troubleshooting guide",{"type":65,"value":363},{"type":60,"tag":1270,"props":1271,"children":1272},"style",{},[1273],{"type":65,"value":1274},"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);}"]