[{"data":1,"prerenderedAt":2196},["ShallowReactive",2],{"navigation_docs":3,"-docs-recipes-protect-admin-routes":493,"-docs-recipes-protect-admin-routes-surround":2191},[4,46,135,218,257,292,355,377,420],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":45},"Getting Started","i-lucide-rocket","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",[10,15,20,25,30,35,40],{"title":11,"path":12,"stem":13,"icon":14},"What is Heximon?","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F01.getting-started\u002F01.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F01.getting-started\u002F02.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","docs\u002F01.getting-started\u002F03.quick-start","i-lucide-zap",{"title":26,"path":27,"stem":28,"icon":29},"Core Concepts","\u002Fdocs\u002Fgetting-started\u002Fcore-concepts","docs\u002F01.getting-started\u002F04.core-concepts","i-lucide-shapes",{"title":31,"path":32,"stem":33,"icon":34},"Examples","\u002Fdocs\u002Fgetting-started\u002Fexamples","docs\u002F01.getting-started\u002F05.examples","i-lucide-library",{"title":36,"path":37,"stem":38,"icon":39},"Where Next?","\u002Fdocs\u002Fgetting-started\u002Fwhere-next","docs\u002F01.getting-started\u002F06.where-next","i-lucide-signpost",{"title":41,"path":42,"stem":43,"icon":44},"Troubleshooting","\u002Fdocs\u002Fgetting-started\u002Ftroubleshooting","docs\u002F01.getting-started\u002F07.troubleshooting","i-lucide-life-buoy",false,{"title":47,"icon":48,"path":49,"stem":50,"children":51,"page":45},"Essentials","i-lucide-boxes","\u002Fdocs\u002Fessentials","docs\u002F02.essentials",[52,56,61,66,71,76,81,86,91,96,101,105,110,115,120,125,130],{"title":53,"path":54,"stem":55,"icon":48},"Modules & DI","\u002Fdocs\u002Fessentials\u002Fmodules-and-di","docs\u002F02.essentials\u002F01.modules-and-di",{"title":57,"path":58,"stem":59,"icon":60},"Controllers","\u002Fdocs\u002Fessentials\u002Fcontrollers","docs\u002F02.essentials\u002F02.controllers","i-lucide-route",{"title":62,"path":63,"stem":64,"icon":65},"Validation & DTOs","\u002Fdocs\u002Fessentials\u002Fvalidation","docs\u002F02.essentials\u002F03.validation","i-lucide-shield-check",{"title":67,"path":68,"stem":69,"icon":70},"Contracts","\u002Fdocs\u002Fessentials\u002Fcontracts","docs\u002F02.essentials\u002F04.contracts","i-lucide-file-signature",{"title":72,"path":73,"stem":74,"icon":75},"REST Client","\u002Fdocs\u002Fessentials\u002Fclient","docs\u002F02.essentials\u002F05.client","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":80},"Database","\u002Fdocs\u002Fessentials\u002Fdatabase","docs\u002F02.essentials\u002F06.database","i-lucide-database",{"title":82,"path":83,"stem":84,"icon":85},"Drizzle ORM","\u002Fdocs\u002Fessentials\u002Fdrizzle","docs\u002F02.essentials\u002F07.drizzle","i-lucide-database-zap",{"title":87,"path":88,"stem":89,"icon":90},"Persistence & Transactions","\u002Fdocs\u002Fessentials\u002Fpersistence","docs\u002F02.essentials\u002F08.persistence","i-lucide-database-backup",{"title":92,"path":93,"stem":94,"icon":95},"Migrations","\u002Fdocs\u002Fessentials\u002Fmigrations","docs\u002F02.essentials\u002F09.migrations","i-lucide-git-branch",{"title":97,"path":98,"stem":99,"icon":100},"Authentication","\u002Fdocs\u002Fessentials\u002Fauthentication","docs\u002F02.essentials\u002F10.authentication","i-lucide-key-round",{"title":102,"path":103,"stem":104,"icon":65},"Permissions & scopes","\u002Fdocs\u002Fessentials\u002Fpermissions","docs\u002F02.essentials\u002F11.permissions",{"title":106,"path":107,"stem":108,"icon":109},"Error Handling","\u002Fdocs\u002Fessentials\u002Ferrors","docs\u002F02.essentials\u002F12.errors","i-lucide-triangle-alert",{"title":111,"path":112,"stem":113,"icon":114},"Testing a Heximon App","\u002Fdocs\u002Fessentials\u002Ftesting","docs\u002F02.essentials\u002F13.testing","i-lucide-flask-conical",{"title":116,"path":117,"stem":118,"icon":119},"Test app harness","\u002Fdocs\u002Fessentials\u002Fcreatetestapp","docs\u002F02.essentials\u002F14.createtestapp","i-lucide-test-tube",{"title":121,"path":122,"stem":123,"icon":124},"Logging & Observability","\u002Fdocs\u002Fessentials\u002Flogging","docs\u002F02.essentials\u002F17.logging","i-lucide-scroll-text",{"title":126,"path":127,"stem":128,"icon":129},"Context & Lifecycle","\u002Fdocs\u002Fessentials\u002Fcontext-and-lifecycle","docs\u002F02.essentials\u002F18.context-and-lifecycle","i-lucide-radio",{"title":131,"path":132,"stem":133,"icon":134},"Project Structure","\u002Fdocs\u002Fessentials\u002Fproject-structure","docs\u002F02.essentials\u002F19.project-structure","i-lucide-folder-tree",{"title":136,"icon":137,"path":138,"stem":139,"children":140,"page":45},"Capabilities","i-lucide-sparkles","\u002Fdocs\u002Fcapabilities","docs\u002F03.capabilities",[141,145,150,155,160,164,169,174,179,184,188,193,198,203,208,213],{"title":142,"path":143,"stem":144,"icon":137},"Overview","\u002Fdocs\u002Fcapabilities\u002Foverview","docs\u002F03.capabilities\u002F01.overview",{"title":146,"path":147,"stem":148,"icon":149},"Events","\u002Fdocs\u002Fcapabilities\u002Fevents","docs\u002F03.capabilities\u002F02.events","i-lucide-radio-tower",{"title":151,"path":152,"stem":153,"icon":154},"Queue","\u002Fdocs\u002Fcapabilities\u002Fbackground-jobs","docs\u002F03.capabilities\u002F03.background-jobs","i-lucide-list-ordered",{"title":156,"path":157,"stem":158,"icon":159},"Scheduled Work","\u002Fdocs\u002Fcapabilities\u002Fscheduled-jobs","docs\u002F03.capabilities\u002F04.scheduled-jobs","i-lucide-clock",{"title":161,"path":162,"stem":163,"icon":85},"Caching","\u002Fdocs\u002Fcapabilities\u002Fcaching","docs\u002F03.capabilities\u002F05.caching",{"title":165,"path":166,"stem":167,"icon":168},"Key\u002FValue Storage","\u002Fdocs\u002Fcapabilities\u002Fkey-value","docs\u002F03.capabilities\u002F06.key-value","i-lucide-hard-drive",{"title":170,"path":171,"stem":172,"icon":173},"Blob Storage","\u002Fdocs\u002Fcapabilities\u002Fblob","docs\u002F03.capabilities\u002F07.blob","i-lucide-folder-open",{"title":175,"path":176,"stem":177,"icon":178},"Health & Readiness","\u002Fdocs\u002Fcapabilities\u002Fhealth","docs\u002F03.capabilities\u002F08.health","i-lucide-heart-pulse",{"title":180,"path":181,"stem":182,"icon":183},"Notifications","\u002Fdocs\u002Fcapabilities\u002Fnotifications","docs\u002F03.capabilities\u002F09.notifications","i-lucide-mail",{"title":185,"path":186,"stem":187,"icon":65},"Idempotency","\u002Fdocs\u002Fcapabilities\u002Fidempotency","docs\u002F03.capabilities\u002F10.idempotency",{"title":189,"path":190,"stem":191,"icon":192},"Security Hardening","\u002Fdocs\u002Fcapabilities\u002Fsecurity","docs\u002F03.capabilities\u002F11.security","i-lucide-shield",{"title":194,"path":195,"stem":196,"icon":197},"Observability & Tracing","\u002Fdocs\u002Fcapabilities\u002Fobservability","docs\u002F03.capabilities\u002F12.observability","i-lucide-activity",{"title":199,"path":200,"stem":201,"icon":202},"OpenAPI","\u002Fdocs\u002Fcapabilities\u002Fopenapi","docs\u002F03.capabilities\u002F13.openapi","i-lucide-book-marked",{"title":204,"path":205,"stem":206,"icon":207},"MCP Server","\u002Fdocs\u002Fcapabilities\u002Fmcp","docs\u002F03.capabilities\u002F14.mcp","i-lucide-bot",{"title":209,"path":210,"stem":211,"icon":212},"Schema DTOs","\u002Fdocs\u002Fcapabilities\u002Fschema","docs\u002F03.capabilities\u002F15.schema","i-lucide-file-json",{"title":214,"path":215,"stem":216,"icon":217},"Branded IDs","\u002Fdocs\u002Fcapabilities\u002Fbranded-ids","docs\u002F03.capabilities\u002F16.branded-ids","i-lucide-tag",{"title":219,"icon":220,"path":221,"stem":222,"children":223,"page":45},"Architecture","i-lucide-building","\u002Fdocs\u002Farchitecture","docs\u002F04.architecture",[224,227,232,237,242,247,252],{"title":142,"path":225,"stem":226,"icon":220},"\u002Fdocs\u002Farchitecture\u002Foverview","docs\u002F04.architecture\u002F01.overview",{"title":228,"path":229,"stem":230,"icon":231},"Domain-Driven Design","\u002Fdocs\u002Farchitecture\u002Fddd","docs\u002F04.architecture\u002F02.ddd","i-lucide-hexagon",{"title":233,"path":234,"stem":235,"icon":236},"CQRS","\u002Fdocs\u002Farchitecture\u002Fcqrs","docs\u002F04.architecture\u002F03.cqrs","i-lucide-split",{"title":238,"path":239,"stem":240,"icon":241},"Repository Pattern","\u002Fdocs\u002Farchitecture\u002Frepository-pattern","docs\u002F04.architecture\u002F04.repository-pattern","i-lucide-archive",{"title":243,"path":244,"stem":245,"icon":246},"Integration Events","\u002Fdocs\u002Farchitecture\u002Fintegration-events","docs\u002F04.architecture\u002F05.integration-events","i-lucide-send-horizontal",{"title":248,"path":249,"stem":250,"icon":251},"Saga Orchestration","\u002Fdocs\u002Farchitecture\u002Fsagas","docs\u002F04.architecture\u002F06.sagas","i-lucide-hourglass",{"title":253,"path":254,"stem":255,"icon":256},"Durable Workflows","\u002Fdocs\u002Farchitecture\u002Fworkflows","docs\u002F04.architecture\u002F07.workflows","i-lucide-workflow",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":45},"Realtime","i-lucide-cable","\u002Fdocs\u002Frealtime","docs\u002F05.realtime",[263,266,270,274,279,284,288],{"title":142,"path":264,"stem":265,"icon":259},"\u002Fdocs\u002Frealtime\u002Foverview","docs\u002F05.realtime\u002F01.overview",{"title":267,"path":268,"stem":269,"icon":129},"WebSockets","\u002Fdocs\u002Frealtime\u002Fwebsockets","docs\u002F05.realtime\u002F02.websockets",{"title":271,"path":272,"stem":273,"icon":70},"WebSocket Contracts","\u002Fdocs\u002Frealtime\u002Fwebsocket-contracts","docs\u002F05.realtime\u002F03.websocket-contracts",{"title":275,"path":276,"stem":277,"icon":278},"Server-Sent Events","\u002Fdocs\u002Frealtime\u002Fserver-sent-events","docs\u002F05.realtime\u002F04.server-sent-events","i-lucide-rss",{"title":280,"path":281,"stem":282,"icon":283},"Typed Clients","\u002Fdocs\u002Frealtime\u002Ftyped-clients","docs\u002F05.realtime\u002F05.typed-clients","i-lucide-plug-zap",{"title":285,"path":286,"stem":287,"icon":259},"AsyncAPI","\u002Fdocs\u002Frealtime\u002Fasyncapi","docs\u002F05.realtime\u002F06.asyncapi",{"title":289,"path":290,"stem":291,"icon":80},"Durable Objects","\u002Fdocs\u002Frealtime\u002Fdurable-objects","docs\u002F05.realtime\u002F07.durable-objects",{"title":293,"icon":294,"path":295,"stem":296,"children":297,"page":45},"Deploy","i-lucide-cloud-upload","\u002Fdocs\u002Fdeploy","docs\u002F06.deploy",[298,301,306,311,316,321,326,331,336,341,345,350],{"title":142,"path":299,"stem":300,"icon":6},"\u002Fdocs\u002Fdeploy\u002Foverview","docs\u002F06.deploy\u002F01.overview",{"title":302,"path":303,"stem":304,"icon":305},"Node","\u002Fdocs\u002Fdeploy\u002Fnode","docs\u002F06.deploy\u002F02.node","la:node-js",{"title":307,"path":308,"stem":309,"icon":310},"Cloudflare","\u002Fdocs\u002Fdeploy\u002Fcloudflare","docs\u002F06.deploy\u002F03.cloudflare","simple-icons:cloudflare",{"title":312,"path":313,"stem":314,"icon":315},"Vercel","\u002Fdocs\u002Fdeploy\u002Fvercel","docs\u002F06.deploy\u002F04.vercel","ri:vercel-fill",{"title":317,"path":318,"stem":319,"icon":320},"Netlify","\u002Fdocs\u002Fdeploy\u002Fnetlify","docs\u002F06.deploy\u002F05.netlify","i-simple-icons-netlify",{"title":322,"path":323,"stem":324,"icon":325},"AWS Lambda","\u002Fdocs\u002Fdeploy\u002Faws-lambda","docs\u002F06.deploy\u002F06.aws-lambda","hugeicons:aws-lambda",{"title":327,"path":328,"stem":329,"icon":330},"Deno Deploy","\u002Fdocs\u002Fdeploy\u002Fdeno-deploy","docs\u002F06.deploy\u002F07.deno-deploy","simple-icons:denodeploy",{"title":332,"path":333,"stem":334,"icon":335},"Nitro","\u002Fdocs\u002Fdeploy\u002Fnitro","docs\u002F06.deploy\u002F08.nitro","i-unjs-nitro",{"title":337,"path":338,"stem":339,"icon":340},"Nuxt","\u002Fdocs\u002Fdeploy\u002Fnuxt","docs\u002F06.deploy\u002F09.nuxt","i-devicon-nuxt",{"title":342,"path":343,"stem":344,"icon":236},"Mixed-runtime split","\u002Fdocs\u002Fdeploy\u002Fmixed-runtime-split","docs\u002F06.deploy\u002F10.mixed-runtime-split",{"title":346,"path":347,"stem":348,"icon":349},"Hosts","\u002Fdocs\u002Fdeploy\u002Fhosts","docs\u002F06.deploy\u002F11.hosts","i-lucide-map",{"title":351,"path":352,"stem":353,"icon":354},"Cross-service events","\u002Fdocs\u002Fdeploy\u002Fcross-service-events","docs\u002F06.deploy\u002F12.cross-service-events","i-lucide-network",{"title":356,"icon":357,"path":358,"stem":359,"children":360,"page":45},"CLI","i-lucide-terminal","\u002Fdocs\u002Fcli","docs\u002F07.cli",[361,365,369,373],{"title":362,"path":363,"stem":364,"icon":357},"The heximon CLI","\u002Fdocs\u002Fcli\u002Foverview","docs\u002F07.cli\u002F01.overview",{"title":366,"path":367,"stem":368,"icon":137},"Create a New App","\u002Fdocs\u002Fcli\u002Fcreate","docs\u002F07.cli\u002F02.create",{"title":370,"path":371,"stem":372,"icon":95},"Migrations from the CLI","\u002Fdocs\u002Fcli\u002Fmigrations","docs\u002F07.cli\u002F03.migrations",{"title":374,"path":375,"stem":376,"icon":6},"Deploying a Heximon app","\u002Fdocs\u002Fcli\u002Fdeploy","docs\u002F07.cli\u002F04.deploy",{"title":378,"icon":379,"path":380,"stem":381,"children":382,"page":45},"Under the Hood","i-lucide-wrench","\u002Fdocs\u002Funder-the-hood","docs\u002F08.under-the-hood",[383,388,393,397,402,407,411,415],{"title":384,"path":385,"stem":386,"icon":387},"How the Compiler Works","\u002Fdocs\u002Funder-the-hood\u002Fhow-the-compiler-works","docs\u002F08.under-the-hood\u002F01.how-the-compiler-works","i-lucide-cog",{"title":389,"path":390,"stem":391,"icon":392},"Compiler","\u002Fdocs\u002Funder-the-hood\u002Fextending-the-compiler","docs\u002F08.under-the-hood\u002F02.extending-the-compiler","i-lucide-cpu",{"title":394,"path":395,"stem":396,"icon":357},"Compiler Diagnostics","\u002Fdocs\u002Funder-the-hood\u002Fcompiler-diagnostics","docs\u002F08.under-the-hood\u002F03.compiler-diagnostics",{"title":398,"path":399,"stem":400,"icon":401},"Library Mode","\u002Fdocs\u002Funder-the-hood\u002Flibrary-mode","docs\u002F08.under-the-hood\u002F04.library-mode","i-lucide-package",{"title":403,"path":404,"stem":405,"icon":406},"Other bundlers","\u002Fdocs\u002Funder-the-hood\u002Falternate-bundlers","docs\u002F08.under-the-hood\u002F05.alternate-bundlers","i-lucide-blocks",{"title":408,"path":409,"stem":410,"icon":401},"Packages","\u002Fdocs\u002Funder-the-hood\u002Fpackages","docs\u002F08.under-the-hood\u002F06.packages",{"title":412,"path":413,"stem":414,"icon":207},"AI Skills","\u002Fdocs\u002Funder-the-hood\u002Fai-and-agents","docs\u002F08.under-the-hood\u002F07.ai-and-agents",{"title":416,"path":417,"stem":418,"icon":419},"Advanced DI","\u002Fdocs\u002Funder-the-hood\u002Fadvanced-di","docs\u002F08.under-the-hood\u002F08.advanced-di","i-lucide-git-fork",{"title":421,"icon":422,"path":423,"stem":424,"children":425,"page":45},"Recipes","i-lucide-book-open-check","\u002Fdocs\u002Frecipes","docs\u002F09.recipes",[426,431,436,440,444,448,452,456,460,465,469,474,479,484,488],{"title":427,"path":428,"stem":429,"icon":430},"Send an Email","\u002Fdocs\u002Frecipes\u002Fsend-an-email","docs\u002F09.recipes\u002F01.send-an-email","i-lucide-send",{"title":432,"path":433,"stem":434,"icon":435},"Upload a File","\u002Fdocs\u002Frecipes\u002Fupload-a-file","docs\u002F09.recipes\u002F02.upload-a-file","i-lucide-upload",{"title":437,"path":438,"stem":439,"icon":24},"Cache an Expensive Call","\u002Fdocs\u002Frecipes\u002Fcache-an-expensive-call","docs\u002F09.recipes\u002F03.cache-an-expensive-call",{"title":441,"path":442,"stem":443,"icon":154},"Paginate a List","\u002Fdocs\u002Frecipes\u002Fpaginate-a-list","docs\u002F09.recipes\u002F04.paginate-a-list",{"title":445,"path":446,"stem":447,"icon":65},"Protect Admin Routes","\u002Fdocs\u002Frecipes\u002Fprotect-admin-routes","docs\u002F09.recipes\u002F05.protect-admin-routes",{"title":449,"path":450,"stem":451,"icon":178},"Health Probes and a Nightly Job","\u002Fdocs\u002Frecipes\u002Fhealth-probes-and-a-nightly-job","docs\u002F09.recipes\u002F06.health-probes-and-a-nightly-job",{"title":453,"path":454,"stem":455,"icon":80},"Build a CRUD API","\u002Fdocs\u002Frecipes\u002Fcrud-api","docs\u002F09.recipes\u002F07.crud-api",{"title":457,"path":458,"stem":459,"icon":100},"Add Authentication","\u002Fdocs\u002Frecipes\u002Fadd-authentication","docs\u002F09.recipes\u002F08.add-authentication",{"title":461,"path":462,"stem":463,"icon":464},"Background Jobs","\u002Fdocs\u002Frecipes\u002Fbackground-jobs","docs\u002F09.recipes\u002F09.background-jobs","i-lucide-timer",{"title":466,"path":467,"stem":468,"icon":149},"Decouple Features with Events","\u002Fdocs\u002Frecipes\u002Fevent-driven-module","docs\u002F09.recipes\u002F10.event-driven-module",{"title":470,"path":471,"stem":472,"icon":473},"Build a DDD Aggregate","\u002Fdocs\u002Frecipes\u002Fddd-aggregate","docs\u002F09.recipes\u002F11.ddd-aggregate","i-lucide-box",{"title":475,"path":476,"stem":477,"icon":478},"Publish a Feature Package","\u002Fdocs\u002Frecipes\u002Ffeature-package","docs\u002F09.recipes\u002F12.feature-package","i-lucide-package-open",{"title":480,"path":481,"stem":482,"icon":483},"Reliable Integration Events","\u002Fdocs\u002Frecipes\u002Freliable-integration-events","docs\u002F09.recipes\u002F13.reliable-integration-events","i-lucide-inbox",{"title":485,"path":486,"stem":487,"icon":95},"Reactive Choreography","\u002Fdocs\u002Frecipes\u002Freactive-choreography","docs\u002F09.recipes\u002F14.reactive-choreography",{"title":489,"path":490,"stem":491,"icon":492},"Build a Production CRUD API","\u002Fdocs\u002Frecipes\u002Fproduction-crud-api","docs\u002F09.recipes\u002F15.production-crud-api","i-lucide-layers",{"id":494,"title":445,"body":495,"description":2184,"extension":2185,"links":2186,"meta":2187,"navigation":2188,"path":446,"seo":2189,"stem":447,"__hash__":2190},"docs\u002Fdocs\u002F09.recipes\u002F05.protect-admin-routes.md",{"type":496,"value":497,"toc":2175},"minimark",[498,527,532,550,906,915,919,937,1337,1344,1357,1397,1401,1422,1820,1827,1846,1850,1872,1932,1939,1943,2070,2074,2100,2122,2126,2171],[499,500,501,502,506,507,510,511,514,515,518,519,526],"p",{},"An ",[503,504,505],"code",{},"\u002Fadmin\u002Fstats"," route that only a caller holding ",[503,508,509],{},"admin:stats:read"," may reach — everyone else gets a ",[503,512,513],{},"401","\n(no token) or ",[503,516,517],{},"403"," (wrong scopes). You'll reuse an existing auth module's exports rather than re-wiring JWT\nverification, and enforce the scope by hand since the route is declared inline. Every block is grounded in the\nrunnable ",[520,521,525],"a",{"href":522,"rel":523},"https:\u002F\u002Fgithub.com\u002Fheximon\u002Fheximon\u002Ftree\u002Fmain\u002Fexamples\u002Fladder\u002FL07-auth",[524],"nofollow","the ladder's L07 — Auth",".",[528,529,531],"h2",{"id":530},"_1-start-from-a-working-login","1. Start from a working login",[499,533,534,535,537,538,541,542,545,546,549],{},"This recipe assumes the ",[520,536,97],{"href":98}," flow already issues a token: ",[503,539,540],{},"login","\nverifies the password in constant time, then signs the account's granted ",[503,543,544],{},"permissions"," straight into the JWT —\nso the scope check below never needs a database round-trip. The token rides both an HttpOnly ",[503,547,548],{},"accessToken","\ncookie and the response body:",[551,552,558],"pre",{"className":553,"code":554,"filename":555,"language":556,"meta":557,"style":557},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const expiresAt = new Date(Date.now() + AuthController.tokenLifetimeMilliseconds);\nconst token = await this.tokenFactory.create(\n  { sub: account.id, permissions: [...account.permissions] },\n  expiresAt,\n);\n\naction.response.setCookie(\"accessToken\", token.value, {\n  httpOnly: true,\n  sameSite: \"lax\",\n  secure: false,\n  expires: expiresAt,\n});\n\nreturn action.respond(200, {\n  userId: account.id,\n  accessToken: token.value,\n  expiresAt: expiresAt.toISOString(),\n});\n","src\u002Fauth\u002Fauth.controller.ts","ts","",[503,559,560,609,637,683,692,700,707,749,763,781,794,807,817,822,846,862,878,897],{"__ignoreMap":557},[561,562,565,569,573,577,580,584,587,589,592,595,598,601,603,606],"span",{"class":563,"line":564},"line",1,[561,566,568],{"class":567},"spNyl","const",[561,570,572],{"class":571},"sTEyZ"," expiresAt ",[561,574,576],{"class":575},"sMK4o","=",[561,578,579],{"class":575}," new",[561,581,583],{"class":582},"s2Zo4"," Date",[561,585,586],{"class":571},"(Date",[561,588,526],{"class":575},[561,590,591],{"class":582},"now",[561,593,594],{"class":571},"() ",[561,596,597],{"class":575},"+",[561,599,600],{"class":571}," AuthController",[561,602,526],{"class":575},[561,604,605],{"class":571},"tokenLifetimeMilliseconds)",[561,607,608],{"class":575},";\n",[561,610,612,614,617,619,623,626,629,631,634],{"class":563,"line":611},2,[561,613,568],{"class":567},[561,615,616],{"class":571}," token ",[561,618,576],{"class":575},[561,620,622],{"class":621},"s7zQu"," await",[561,624,625],{"class":575}," this.",[561,627,628],{"class":571},"tokenFactory",[561,630,526],{"class":575},[561,632,633],{"class":582},"create",[561,635,636],{"class":571},"(\n",[561,638,640,643,647,650,653,655,658,661,664,666,669,672,675,677,680],{"class":563,"line":639},3,[561,641,642],{"class":575},"  {",[561,644,646],{"class":645},"swJcz"," sub",[561,648,649],{"class":575},":",[561,651,652],{"class":571}," account",[561,654,526],{"class":575},[561,656,657],{"class":571},"id",[561,659,660],{"class":575},",",[561,662,663],{"class":645}," permissions",[561,665,649],{"class":575},[561,667,668],{"class":571}," [",[561,670,671],{"class":575},"...",[561,673,674],{"class":571},"account",[561,676,526],{"class":575},[561,678,679],{"class":571},"permissions] ",[561,681,682],{"class":575},"},\n",[561,684,686,689],{"class":563,"line":685},4,[561,687,688],{"class":571},"  expiresAt",[561,690,691],{"class":575},",\n",[561,693,695,698],{"class":563,"line":694},5,[561,696,697],{"class":571},")",[561,699,608],{"class":575},[561,701,703],{"class":563,"line":702},6,[561,704,706],{"emptyLinePlaceholder":705},true,"\n",[561,708,710,713,715,718,720,723,726,729,732,734,736,739,741,744,746],{"class":563,"line":709},7,[561,711,712],{"class":571},"action",[561,714,526],{"class":575},[561,716,717],{"class":571},"response",[561,719,526],{"class":575},[561,721,722],{"class":582},"setCookie",[561,724,725],{"class":571},"(",[561,727,728],{"class":575},"\"",[561,730,548],{"class":731},"sfazB",[561,733,728],{"class":575},[561,735,660],{"class":575},[561,737,738],{"class":571}," token",[561,740,526],{"class":575},[561,742,743],{"class":571},"value",[561,745,660],{"class":575},[561,747,748],{"class":575}," {\n",[561,750,752,755,757,761],{"class":563,"line":751},8,[561,753,754],{"class":645},"  httpOnly",[561,756,649],{"class":575},[561,758,760],{"class":759},"sfNiH"," true",[561,762,691],{"class":575},[561,764,766,769,771,774,777,779],{"class":563,"line":765},9,[561,767,768],{"class":645},"  sameSite",[561,770,649],{"class":575},[561,772,773],{"class":575}," \"",[561,775,776],{"class":731},"lax",[561,778,728],{"class":575},[561,780,691],{"class":575},[561,782,784,787,789,792],{"class":563,"line":783},10,[561,785,786],{"class":645},"  secure",[561,788,649],{"class":575},[561,790,791],{"class":759}," false",[561,793,691],{"class":575},[561,795,797,800,802,805],{"class":563,"line":796},11,[561,798,799],{"class":645},"  expires",[561,801,649],{"class":575},[561,803,804],{"class":571}," expiresAt",[561,806,691],{"class":575},[561,808,810,813,815],{"class":563,"line":809},12,[561,811,812],{"class":575},"}",[561,814,697],{"class":571},[561,816,608],{"class":575},[561,818,820],{"class":563,"line":819},13,[561,821,706],{"emptyLinePlaceholder":705},[561,823,825,828,831,833,836,838,842,844],{"class":563,"line":824},14,[561,826,827],{"class":621},"return",[561,829,830],{"class":571}," action",[561,832,526],{"class":575},[561,834,835],{"class":582},"respond",[561,837,725],{"class":571},[561,839,841],{"class":840},"sbssI","200",[561,843,660],{"class":575},[561,845,748],{"class":575},[561,847,849,852,854,856,858,860],{"class":563,"line":848},15,[561,850,851],{"class":645},"  userId",[561,853,649],{"class":575},[561,855,652],{"class":571},[561,857,526],{"class":575},[561,859,657],{"class":571},[561,861,691],{"class":575},[561,863,865,868,870,872,874,876],{"class":563,"line":864},16,[561,866,867],{"class":645},"  accessToken",[561,869,649],{"class":575},[561,871,738],{"class":571},[561,873,526],{"class":575},[561,875,743],{"class":571},[561,877,691],{"class":575},[561,879,881,883,885,887,889,892,895],{"class":563,"line":880},17,[561,882,688],{"class":645},[561,884,649],{"class":575},[561,886,804],{"class":571},[561,888,526],{"class":575},[561,890,891],{"class":582},"toISOString",[561,893,894],{"class":571},"()",[561,896,691],{"class":575},[561,898,900,902,904],{"class":563,"line":899},18,[561,901,812],{"class":575},[561,903,697],{"class":571},[561,905,608],{"class":575},[499,907,908],{},[909,910,911,912,697],"em",{},"(Source: ",[503,913,914],{},"examples\u002Fladder\u002FL07-auth\u002Fsrc\u002Fauth\u002Fauth.controller.ts",[528,916,918],{"id":917},"_2-wire-jwtauthmiddleware-once-export-it","2. Wire JWTAuthMiddleware once, export it",[499,920,921,924,925,928,929,933,934,936],{},[503,922,923],{},"JWTAuthMiddleware","'s constructor is only DI tokens (an ",[503,926,927],{},"AuthContext","), so it is a ",[930,931,932],"strong",{},"bare provider"," — list it\ndirectly, no app-local wrapper. Export it (and the ",[503,935,927],{}," it populates) so another feature module can\nreuse the exact same auth wiring.",[551,938,941],{"className":553,"code":939,"filename":940,"language":556,"meta":557,"style":557},"export class AuthModule extends Module({\n  providers: [\n    UserAccountStore,\n    { provide: PasswordHashingAlgorithm, useClass: Scrypt },\n    JWTAuthMiddleware,\n    {\n      provide: IdentityKeyPair,\n      useFactory: (): Promise\u003CIdentityKeyPair> => IdentityKeyPair.resolve(),\n    },\n    {\n      provide: JWTFactory,\n      useFactory: (keys: IdentityKeyPair): JWTFactory => new JWTFactory(keys.privateKey),\n    },\n    {\n      provide: AuthContext,\n      useFactory: (context: Context, verifier: JWTVerifier): AuthContext =>\n        new AuthContext(context, verifier, new CookieTokenSource(\"accessToken\")),\n    },\n  ],\n  http: { controllers: [AuthController, JwksController] },\n  \u002F\u002F Re-exported so feature modules (e.g. the AdminModule) can authenticate their own routes with the SAME\n  \u002F\u002F shipped middleware + read the SAME request-scoped principal, without re-providing any of it.\n  exports: [\n    AuthContext,\n    JWTAuthMiddleware,\n    JWTVerifier,\n    JWTFactory,\n    UserAccountStore,\n    PasswordHashingAlgorithm,\n  ],\n}) {}\n","src\u002Fauth\u002Fauth.module.ts",[503,942,943,966,976,983,1008,1015,1020,1032,1068,1073,1077,1088,1126,1130,1134,1145,1178,1212,1216,1224,1250,1257,1263,1273,1281,1288,1296,1304,1311,1319,1326],{"__ignoreMap":557},[561,944,945,948,951,955,958,961,963],{"class":563,"line":564},[561,946,947],{"class":621},"export",[561,949,950],{"class":567}," class",[561,952,954],{"class":953},"sBMFI"," AuthModule",[561,956,957],{"class":567}," extends",[561,959,960],{"class":582}," Module",[561,962,725],{"class":571},[561,964,965],{"class":575},"{\n",[561,967,968,971,973],{"class":563,"line":611},[561,969,970],{"class":645},"  providers",[561,972,649],{"class":575},[561,974,975],{"class":571}," [\n",[561,977,978,981],{"class":563,"line":639},[561,979,980],{"class":571},"    UserAccountStore",[561,982,691],{"class":575},[561,984,985,988,991,993,996,998,1001,1003,1006],{"class":563,"line":685},[561,986,987],{"class":575},"    {",[561,989,990],{"class":645}," provide",[561,992,649],{"class":575},[561,994,995],{"class":571}," PasswordHashingAlgorithm",[561,997,660],{"class":575},[561,999,1000],{"class":645}," useClass",[561,1002,649],{"class":575},[561,1004,1005],{"class":571}," Scrypt ",[561,1007,682],{"class":575},[561,1009,1010,1013],{"class":563,"line":694},[561,1011,1012],{"class":571},"    JWTAuthMiddleware",[561,1014,691],{"class":575},[561,1016,1017],{"class":563,"line":702},[561,1018,1019],{"class":575},"    {\n",[561,1021,1022,1025,1027,1030],{"class":563,"line":709},[561,1023,1024],{"class":645},"      provide",[561,1026,649],{"class":575},[561,1028,1029],{"class":571}," IdentityKeyPair",[561,1031,691],{"class":575},[561,1033,1034,1037,1039,1042,1045,1048,1051,1054,1057,1059,1061,1064,1066],{"class":563,"line":751},[561,1035,1036],{"class":582},"      useFactory",[561,1038,649],{"class":575},[561,1040,1041],{"class":575}," ():",[561,1043,1044],{"class":953}," Promise",[561,1046,1047],{"class":575},"\u003C",[561,1049,1050],{"class":953},"IdentityKeyPair",[561,1052,1053],{"class":575},">",[561,1055,1056],{"class":567}," =>",[561,1058,1029],{"class":571},[561,1060,526],{"class":575},[561,1062,1063],{"class":582},"resolve",[561,1065,894],{"class":571},[561,1067,691],{"class":575},[561,1069,1070],{"class":563,"line":765},[561,1071,1072],{"class":575},"    },\n",[561,1074,1075],{"class":563,"line":783},[561,1076,1019],{"class":575},[561,1078,1079,1081,1083,1086],{"class":563,"line":796},[561,1080,1024],{"class":645},[561,1082,649],{"class":575},[561,1084,1085],{"class":571}," JWTFactory",[561,1087,691],{"class":575},[561,1089,1090,1092,1094,1097,1101,1103,1105,1108,1110,1112,1114,1116,1119,1121,1124],{"class":563,"line":809},[561,1091,1036],{"class":582},[561,1093,649],{"class":575},[561,1095,1096],{"class":575}," (",[561,1098,1100],{"class":1099},"sHdIc","keys",[561,1102,649],{"class":575},[561,1104,1029],{"class":953},[561,1106,1107],{"class":575},"):",[561,1109,1085],{"class":953},[561,1111,1056],{"class":567},[561,1113,579],{"class":575},[561,1115,1085],{"class":582},[561,1117,1118],{"class":571},"(keys",[561,1120,526],{"class":575},[561,1122,1123],{"class":571},"privateKey)",[561,1125,691],{"class":575},[561,1127,1128],{"class":563,"line":819},[561,1129,1072],{"class":575},[561,1131,1132],{"class":563,"line":824},[561,1133,1019],{"class":575},[561,1135,1136,1138,1140,1143],{"class":563,"line":848},[561,1137,1024],{"class":645},[561,1139,649],{"class":575},[561,1141,1142],{"class":571}," AuthContext",[561,1144,691],{"class":575},[561,1146,1147,1149,1151,1153,1156,1158,1161,1163,1166,1168,1171,1173,1175],{"class":563,"line":864},[561,1148,1036],{"class":582},[561,1150,649],{"class":575},[561,1152,1096],{"class":575},[561,1154,1155],{"class":1099},"context",[561,1157,649],{"class":575},[561,1159,1160],{"class":953}," Context",[561,1162,660],{"class":575},[561,1164,1165],{"class":1099}," verifier",[561,1167,649],{"class":575},[561,1169,1170],{"class":953}," JWTVerifier",[561,1172,1107],{"class":575},[561,1174,1142],{"class":953},[561,1176,1177],{"class":567}," =>\n",[561,1179,1180,1183,1185,1188,1190,1192,1194,1196,1199,1201,1203,1205,1207,1210],{"class":563,"line":880},[561,1181,1182],{"class":575},"        new",[561,1184,1142],{"class":582},[561,1186,1187],{"class":571},"(context",[561,1189,660],{"class":575},[561,1191,1165],{"class":571},[561,1193,660],{"class":575},[561,1195,579],{"class":575},[561,1197,1198],{"class":582}," CookieTokenSource",[561,1200,725],{"class":571},[561,1202,728],{"class":575},[561,1204,548],{"class":731},[561,1206,728],{"class":575},[561,1208,1209],{"class":571},"))",[561,1211,691],{"class":575},[561,1213,1214],{"class":563,"line":899},[561,1215,1072],{"class":575},[561,1217,1219,1222],{"class":563,"line":1218},19,[561,1220,1221],{"class":571},"  ]",[561,1223,691],{"class":575},[561,1225,1227,1230,1232,1235,1238,1240,1243,1245,1248],{"class":563,"line":1226},20,[561,1228,1229],{"class":645},"  http",[561,1231,649],{"class":575},[561,1233,1234],{"class":575}," {",[561,1236,1237],{"class":645}," controllers",[561,1239,649],{"class":575},[561,1241,1242],{"class":571}," [AuthController",[561,1244,660],{"class":575},[561,1246,1247],{"class":571}," JwksController] ",[561,1249,682],{"class":575},[561,1251,1253],{"class":563,"line":1252},21,[561,1254,1256],{"class":1255},"sHwdD","  \u002F\u002F Re-exported so feature modules (e.g. the AdminModule) can authenticate their own routes with the SAME\n",[561,1258,1260],{"class":563,"line":1259},22,[561,1261,1262],{"class":1255},"  \u002F\u002F shipped middleware + read the SAME request-scoped principal, without re-providing any of it.\n",[561,1264,1266,1269,1271],{"class":563,"line":1265},23,[561,1267,1268],{"class":645},"  exports",[561,1270,649],{"class":575},[561,1272,975],{"class":571},[561,1274,1276,1279],{"class":563,"line":1275},24,[561,1277,1278],{"class":571},"    AuthContext",[561,1280,691],{"class":575},[561,1282,1284,1286],{"class":563,"line":1283},25,[561,1285,1012],{"class":571},[561,1287,691],{"class":575},[561,1289,1291,1294],{"class":563,"line":1290},26,[561,1292,1293],{"class":571},"    JWTVerifier",[561,1295,691],{"class":575},[561,1297,1299,1302],{"class":563,"line":1298},27,[561,1300,1301],{"class":571},"    JWTFactory",[561,1303,691],{"class":575},[561,1305,1307,1309],{"class":563,"line":1306},28,[561,1308,980],{"class":571},[561,1310,691],{"class":575},[561,1312,1314,1317],{"class":563,"line":1313},29,[561,1315,1316],{"class":571},"    PasswordHashingAlgorithm",[561,1318,691],{"class":575},[561,1320,1322,1324],{"class":563,"line":1321},30,[561,1323,1221],{"class":571},[561,1325,691],{"class":575},[561,1327,1329,1331,1334],{"class":563,"line":1328},31,[561,1330,812],{"class":575},[561,1332,1333],{"class":571},") ",[561,1335,1336],{"class":575},"{}\n",[499,1338,1339],{},[909,1340,911,1341,697],{},[503,1342,1343],{},"examples\u002Fladder\u002FL07-auth\u002Fsrc\u002Fauth\u002Fauth.module.ts",[499,1345,1346,1349,1350,1353,1354,526],{},[503,1347,1348],{},"IdentityKeyPair.resolve()"," reads a Base64-encoded private JWK from ",[503,1351,1352],{},"HEXIMON_JWK"," in production (surviving a\nrestart or scale-out) and only falls back to a freshly generated ES256 pair in dev — see\n",[520,1355,97],{"href":1356},"\u002Fdocs\u002Fessentials\u002Fauthentication#generate-a-key-pair-at-boot",[499,1358,1359,1360,1363,1364,1367,1368,1371,1372,1374,1375,1377,1378,1380,1381,1383,1384,1387,1388,1391,1392,1396],{},"There is also no separate\n",[503,1361,1362],{},"JWTVerifier"," provider: ",[503,1365,1366],{},"JWTFactory extends JWTVerifier"," (it verifies with the same key it signs with), so\nproviding ",[503,1369,1370],{},"JWTFactory"," alone satisfies every ",[503,1373,1362],{},"-typed injection and export — ",[503,1376,927],{},"'s\nfactory still asks for a ",[503,1379,1362],{}," (the capability it needs), and the compiler resolves it to the\n",[503,1382,1370],{}," instance. ",[503,1385,1386],{},"JwksController"," (also listed above) publishes the public half at\n",[503,1389,1390],{},"\u002F.well-known\u002Fjwks.json"," — see Authentication's ",[520,1393,1395],{"href":1394},"\u002Fdocs\u002Fessentials\u002Fauthentication#publish-a-jwks","Publish a JWKS","\nsection.",[528,1398,1400],{"id":1399},"_3-gate-the-admin-route-by-scope","3. Gate the admin route by scope",[499,1402,1403,1404,1407,1408,1411,1412,1415,1416,1418,1419,1421],{},"Inline routes — declared by the handler's ",[503,1405,1406],{},"Get\u003C...>"," parameter type — carry no ",[503,1409,1410],{},"scopes",", so there's nothing for\nan automatic guard to read. Check the scope by hand through ",[503,1413,1414],{},"AuthContext.isAllowed",": it reads the same\nprincipal a guard would and produces the same ",[503,1417,513],{}," (no principal) \u002F ",[503,1420,517],{}," (under-scoped) split.",[551,1423,1426],{"className":553,"code":1424,"filename":1425,"language":556,"meta":557,"style":557},"export class AdminController implements Controller\u003C{\n  prefix: \"\u002Fadmin\";\n  middlewares: [JWTAuthMiddleware];\n}> {\n  private static readonly requiredScope: string = \"admin:stats:read\";\n\n  public constructor(private readonly authContext: AuthContext) {}\n\n  public async stats(action: Get\u003C\"\u002Fstats\">): Promise\u003CResponse> {\n    if (this.authContext.principal() === null) {\n      return action.respond(401, { error: \"Authentication is required.\" });\n    }\n\n    if (!this.authContext.isAllowed(AdminController.requiredScope)) {\n      return action.respond(403, {\n        error: `Requires the '${AdminController.requiredScope}' scope.`,\n      });\n    }\n\n    return action.respond(200, { totalRequests: 42, activeSessions: 7 });\n  }\n}\n","src\u002Fadmin\u002Fadmin.controller.ts",[503,1427,1428,1446,1462,1478,1485,1515,1519,1546,1550,1592,1622,1660,1665,1669,1700,1718,1750,1759,1763,1767,1810,1815],{"__ignoreMap":557},[561,1429,1430,1432,1434,1437,1440,1443],{"class":563,"line":564},[561,1431,947],{"class":621},[561,1433,950],{"class":567},[561,1435,1436],{"class":953}," AdminController",[561,1438,1439],{"class":567}," implements",[561,1441,1442],{"class":582}," Controller",[561,1444,1445],{"class":575},"\u003C{\n",[561,1447,1448,1451,1453,1455,1458,1460],{"class":563,"line":611},[561,1449,1450],{"class":645},"  prefix",[561,1452,649],{"class":575},[561,1454,773],{"class":575},[561,1456,1457],{"class":731},"\u002Fadmin",[561,1459,728],{"class":575},[561,1461,608],{"class":575},[561,1463,1464,1467,1469,1471,1473,1476],{"class":563,"line":639},[561,1465,1466],{"class":645},"  middlewares",[561,1468,649],{"class":575},[561,1470,668],{"class":571},[561,1472,923],{"class":953},[561,1474,1475],{"class":571},"]",[561,1477,608],{"class":575},[561,1479,1480,1483],{"class":563,"line":685},[561,1481,1482],{"class":575},"}>",[561,1484,748],{"class":575},[561,1486,1487,1490,1493,1496,1499,1501,1504,1507,1509,1511,1513],{"class":563,"line":694},[561,1488,1489],{"class":567},"  private",[561,1491,1492],{"class":567}," static",[561,1494,1495],{"class":567}," readonly",[561,1497,1498],{"class":645}," requiredScope",[561,1500,649],{"class":575},[561,1502,1503],{"class":953}," string",[561,1505,1506],{"class":575}," =",[561,1508,773],{"class":575},[561,1510,509],{"class":731},[561,1512,728],{"class":575},[561,1514,608],{"class":575},[561,1516,1517],{"class":563,"line":702},[561,1518,706],{"emptyLinePlaceholder":705},[561,1520,1521,1524,1527,1529,1532,1534,1537,1539,1541,1543],{"class":563,"line":709},[561,1522,1523],{"class":567},"  public",[561,1525,1526],{"class":567}," constructor",[561,1528,725],{"class":575},[561,1530,1531],{"class":567},"private",[561,1533,1495],{"class":567},[561,1535,1536],{"class":1099}," authContext",[561,1538,649],{"class":575},[561,1540,1142],{"class":953},[561,1542,697],{"class":575},[561,1544,1545],{"class":575}," {}\n",[561,1547,1548],{"class":563,"line":751},[561,1549,706],{"emptyLinePlaceholder":705},[561,1551,1552,1554,1557,1560,1562,1564,1566,1569,1571,1573,1576,1578,1581,1583,1585,1588,1590],{"class":563,"line":765},[561,1553,1523],{"class":567},[561,1555,1556],{"class":567}," async",[561,1558,1559],{"class":645}," stats",[561,1561,725],{"class":575},[561,1563,712],{"class":1099},[561,1565,649],{"class":575},[561,1567,1568],{"class":953}," Get",[561,1570,1047],{"class":575},[561,1572,728],{"class":575},[561,1574,1575],{"class":731},"\u002Fstats",[561,1577,728],{"class":575},[561,1579,1580],{"class":575},">):",[561,1582,1044],{"class":953},[561,1584,1047],{"class":575},[561,1586,1587],{"class":953},"Response",[561,1589,1053],{"class":575},[561,1591,748],{"class":575},[561,1593,1594,1597,1599,1602,1605,1607,1610,1612,1615,1618,1620],{"class":563,"line":783},[561,1595,1596],{"class":621},"    if",[561,1598,1096],{"class":645},[561,1600,1601],{"class":575},"this.",[561,1603,1604],{"class":571},"authContext",[561,1606,526],{"class":575},[561,1608,1609],{"class":582},"principal",[561,1611,594],{"class":645},[561,1613,1614],{"class":575},"===",[561,1616,1617],{"class":575}," null",[561,1619,1333],{"class":645},[561,1621,965],{"class":575},[561,1623,1624,1627,1629,1631,1633,1635,1637,1639,1641,1644,1646,1648,1651,1653,1656,1658],{"class":563,"line":796},[561,1625,1626],{"class":621},"      return",[561,1628,830],{"class":571},[561,1630,526],{"class":575},[561,1632,835],{"class":582},[561,1634,725],{"class":645},[561,1636,513],{"class":840},[561,1638,660],{"class":575},[561,1640,1234],{"class":575},[561,1642,1643],{"class":645}," error",[561,1645,649],{"class":575},[561,1647,773],{"class":575},[561,1649,1650],{"class":731},"Authentication is required.",[561,1652,728],{"class":575},[561,1654,1655],{"class":575}," }",[561,1657,697],{"class":645},[561,1659,608],{"class":575},[561,1661,1662],{"class":563,"line":809},[561,1663,1664],{"class":575},"    }\n",[561,1666,1667],{"class":563,"line":819},[561,1668,706],{"emptyLinePlaceholder":705},[561,1670,1671,1673,1675,1678,1680,1682,1685,1687,1690,1692,1695,1698],{"class":563,"line":824},[561,1672,1596],{"class":621},[561,1674,1096],{"class":645},[561,1676,1677],{"class":575},"!this.",[561,1679,1604],{"class":571},[561,1681,526],{"class":575},[561,1683,1684],{"class":582},"isAllowed",[561,1686,725],{"class":645},[561,1688,1689],{"class":571},"AdminController",[561,1691,526],{"class":575},[561,1693,1694],{"class":571},"requiredScope",[561,1696,1697],{"class":645},")) ",[561,1699,965],{"class":575},[561,1701,1702,1704,1706,1708,1710,1712,1714,1716],{"class":563,"line":848},[561,1703,1626],{"class":621},[561,1705,830],{"class":571},[561,1707,526],{"class":575},[561,1709,835],{"class":582},[561,1711,725],{"class":645},[561,1713,517],{"class":840},[561,1715,660],{"class":575},[561,1717,748],{"class":575},[561,1719,1720,1723,1725,1728,1731,1734,1736,1738,1740,1742,1745,1748],{"class":563,"line":864},[561,1721,1722],{"class":645},"        error",[561,1724,649],{"class":575},[561,1726,1727],{"class":575}," `",[561,1729,1730],{"class":731},"Requires the '",[561,1732,1733],{"class":575},"${",[561,1735,1689],{"class":571},[561,1737,526],{"class":575},[561,1739,1694],{"class":571},[561,1741,812],{"class":575},[561,1743,1744],{"class":731},"' scope.",[561,1746,1747],{"class":575},"`",[561,1749,691],{"class":575},[561,1751,1752,1755,1757],{"class":563,"line":880},[561,1753,1754],{"class":575},"      }",[561,1756,697],{"class":645},[561,1758,608],{"class":575},[561,1760,1761],{"class":563,"line":899},[561,1762,1664],{"class":575},[561,1764,1765],{"class":563,"line":1218},[561,1766,706],{"emptyLinePlaceholder":705},[561,1768,1769,1772,1774,1776,1778,1780,1782,1784,1786,1789,1791,1794,1796,1799,1801,1804,1806,1808],{"class":563,"line":1226},[561,1770,1771],{"class":621},"    return",[561,1773,830],{"class":571},[561,1775,526],{"class":575},[561,1777,835],{"class":582},[561,1779,725],{"class":645},[561,1781,841],{"class":840},[561,1783,660],{"class":575},[561,1785,1234],{"class":575},[561,1787,1788],{"class":645}," totalRequests",[561,1790,649],{"class":575},[561,1792,1793],{"class":840}," 42",[561,1795,660],{"class":575},[561,1797,1798],{"class":645}," activeSessions",[561,1800,649],{"class":575},[561,1802,1803],{"class":840}," 7",[561,1805,1655],{"class":575},[561,1807,697],{"class":645},[561,1809,608],{"class":575},[561,1811,1812],{"class":563,"line":1252},[561,1813,1814],{"class":575},"  }\n",[561,1816,1817],{"class":563,"line":1259},[561,1818,1819],{"class":575},"}\n",[499,1821,1822],{},[909,1823,911,1824,697],{},[503,1825,1826],{},"examples\u002Fladder\u002FL07-auth\u002Fsrc\u002Fadmin\u002Fadmin.controller.ts",[499,1828,1829,1830,1833,1834,1837,1838,1841,1842,1845],{},"A ",[930,1831,1832],{},"contract"," route gets this for free instead: declare ",[503,1835,1836],{},"Route.get(\"\u002Fstats\").scopes(\"admin:stats:read\")"," and\nlet the shipped ",[503,1839,1840],{},"GuardMiddleware"," reject before your handler runs — see\n",[520,1843,102],{"href":1844},"\u002Fdocs\u002Fessentials\u002Fpermissions#enforce-with-guardmiddleware"," for that path.",[528,1847,1849],{"id":1848},"_4-import-the-auth-module-dont-re-provide-it","4. Import the auth module, don't re-provide it",[499,1851,1852,1855,1856,1859,1860,1862,1863,1865,1866,1868,1869,1871],{},[503,1853,1854],{},"AdminModule"," imports ",[503,1857,1858],{},"AuthModule"," and lists only its own controller. The compiler wires ",[503,1861,1689],{},"'s\n",[503,1864,927],{}," from ",[503,1867,1858],{},"'s exports — no duplicate key pair, no second ",[503,1870,923],{}," instance.",[551,1873,1876],{"className":553,"code":1874,"filename":1875,"language":556,"meta":557,"style":557},"export class AdminModule extends Module({\n  imports: [AuthModule],\n  http: { controllers: [AdminController] },\n}) {}\n","src\u002Fadmin\u002Fadmin.module.ts",[503,1877,1878,1895,1907,1924],{"__ignoreMap":557},[561,1879,1880,1882,1884,1887,1889,1891,1893],{"class":563,"line":564},[561,1881,947],{"class":621},[561,1883,950],{"class":567},[561,1885,1886],{"class":953}," AdminModule",[561,1888,957],{"class":567},[561,1890,960],{"class":582},[561,1892,725],{"class":571},[561,1894,965],{"class":575},[561,1896,1897,1900,1902,1905],{"class":563,"line":611},[561,1898,1899],{"class":645},"  imports",[561,1901,649],{"class":575},[561,1903,1904],{"class":571}," [AuthModule]",[561,1906,691],{"class":575},[561,1908,1909,1911,1913,1915,1917,1919,1922],{"class":563,"line":639},[561,1910,1229],{"class":645},[561,1912,649],{"class":575},[561,1914,1234],{"class":575},[561,1916,1237],{"class":645},[561,1918,649],{"class":575},[561,1920,1921],{"class":571}," [AdminController] ",[561,1923,682],{"class":575},[561,1925,1926,1928,1930],{"class":563,"line":685},[561,1927,812],{"class":575},[561,1929,1333],{"class":571},[561,1931,1336],{"class":575},[499,1933,1934],{},[909,1935,911,1936,697],{},[503,1937,1938],{},"examples\u002Fladder\u002FL07-auth\u002Fsrc\u002Fadmin\u002Fadmin.module.ts",[528,1940,1942],{"id":1941},"_5-run-it","5. Run it",[551,1944,1949],{"className":1945,"code":1946,"filename":1947,"language":1948,"meta":557,"style":557},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","TOKEN=$(curl -s -X POST localhost:3000\u002Fauth\u002Flogin \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"email\":\"alice@example.com\",\"password\":\"correct-horse-battery\"}' | jq -r .accessToken)\n\ncurl -s localhost:3000\u002Fadmin\u002Fstats -H \"cookie: accessToken=$TOKEN\"\n# -> 403: alice only holds profile:read, not admin:stats:read\n\ncurl -s localhost:3000\u002Fadmin\u002Fstats\n# -> 401: no token at all\n","terminal","bash",[503,1950,1951,1977,1993,2020,2024,2047,2052,2056,2065],{"__ignoreMap":557},[561,1952,1953,1956,1959,1962,1965,1968,1971,1974],{"class":563,"line":564},[561,1954,1955],{"class":571},"TOKEN",[561,1957,1958],{"class":575},"=$(",[561,1960,1961],{"class":953},"curl",[561,1963,1964],{"class":731}," -s",[561,1966,1967],{"class":731}," -X",[561,1969,1970],{"class":731}," POST",[561,1972,1973],{"class":731}," localhost:3000\u002Fauth\u002Flogin",[561,1975,1976],{"class":571}," \\\n",[561,1978,1979,1982,1985,1988,1991],{"class":563,"line":611},[561,1980,1981],{"class":731},"  -H",[561,1983,1984],{"class":575}," '",[561,1986,1987],{"class":731},"content-type: application\u002Fjson",[561,1989,1990],{"class":575},"'",[561,1992,1976],{"class":571},[561,1994,1995,1998,2000,2003,2005,2008,2011,2014,2017],{"class":563,"line":639},[561,1996,1997],{"class":731},"  -d",[561,1999,1984],{"class":575},[561,2001,2002],{"class":731},"{\"email\":\"alice@example.com\",\"password\":\"correct-horse-battery\"}",[561,2004,1990],{"class":575},[561,2006,2007],{"class":575}," |",[561,2009,2010],{"class":953}," jq",[561,2012,2013],{"class":731}," -r",[561,2015,2016],{"class":731}," .accessToken",[561,2018,2019],{"class":575},")\n",[561,2021,2022],{"class":563,"line":685},[561,2023,706],{"emptyLinePlaceholder":705},[561,2025,2026,2028,2030,2033,2036,2038,2041,2044],{"class":563,"line":694},[561,2027,1961],{"class":953},[561,2029,1964],{"class":731},[561,2031,2032],{"class":731}," localhost:3000\u002Fadmin\u002Fstats",[561,2034,2035],{"class":731}," -H",[561,2037,773],{"class":575},[561,2039,2040],{"class":731},"cookie: accessToken=",[561,2042,2043],{"class":571},"$TOKEN",[561,2045,2046],{"class":575},"\"\n",[561,2048,2049],{"class":563,"line":702},[561,2050,2051],{"class":1255},"# -> 403: alice only holds profile:read, not admin:stats:read\n",[561,2053,2054],{"class":563,"line":709},[561,2055,706],{"emptyLinePlaceholder":705},[561,2057,2058,2060,2062],{"class":563,"line":751},[561,2059,1961],{"class":953},[561,2061,1964],{"class":731},[561,2063,2064],{"class":731}," localhost:3000\u002Fadmin\u002Fstats\n",[561,2066,2067],{"class":563,"line":765},[561,2068,2069],{"class":1255},"# -> 401: no token at all\n",[528,2071,2073],{"id":2072},"pitfalls","Pitfalls",[499,2075,2076,2086,2087,2089,2090,2092,2093,2096,2097,2099],{},[930,2077,2078,2079,2081,2082,2085],{},"Checking ",[503,2080,1684],{}," without checking ",[503,2083,2084],{},"principal()"," first."," An anonymous caller and an under-scoped caller\nare different failures (",[503,2088,513],{}," vs ",[503,2091,517],{},"). Skip the ",[503,2094,2095],{},"principal() === null"," check and a request with no token at\nall silently falls through to the ",[503,2098,517],{}," branch, which is the wrong signal for a client deciding whether to\nprompt for login versus show a permission error.",[499,2101,2102,2105,2106,2108,2109,2111,2112,2114,2115,2117,2118,2121],{},[930,2103,2104],{},"Forgetting to export from the auth module."," ",[503,2107,1854],{}," only compiles because ",[503,2110,1858],{}," lists\n",[503,2113,927],{}," and ",[503,2116,923],{}," under ",[503,2119,2120],{},"exports",". Miss the export and the importing module can't resolve\neither dependency — a build-time error, not a runtime surprise.",[528,2123,2125],{"id":2124},"see-also","See also",[2127,2128,2129,2141,2149,2154],"ul",{},[2130,2131,2132,2134,2135,2137,2138,526],"li",{},[520,2133,97],{"href":98}," — the full register\u002Flogin flow this recipe starts from,\nincluding ",[503,2136,1050],{}," generation and ",[503,2139,2140],{},"PasswordHashingAlgorithm",[2130,2142,2143,2145,2146,2148],{},[520,2144,102],{"href":103}," — the contract-route form of this same scope check,\nenforced automatically by ",[503,2147,1840],{}," instead of by hand.",[2130,2150,2151,2153],{},[520,2152,457],{"href":458}," — the longer end-to-end recipe this one assumes,\ncovering registration, password hashing, and token issuance from scratch.",[2130,2155,2156,2162,2163,2165,2166,2165,2168,2170],{},[520,2157,2159],{"href":522,"rel":2158},[524],[930,2160,2161],{},"Example L07 — auth"," — the\nrunnable source, with an end-to-end test proving the full ",[503,2164,513],{}," \u002F ",[503,2167,517],{},[503,2169,841],{}," scope ladder.",[2172,2173,2174],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":557,"searchDepth":611,"depth":611,"links":2176},[2177,2178,2179,2180,2181,2182,2183],{"id":530,"depth":611,"text":531},{"id":917,"depth":611,"text":918},{"id":1399,"depth":611,"text":1400},{"id":1848,"depth":611,"text":1849},{"id":1941,"depth":611,"text":1942},{"id":2072,"depth":611,"text":2073},{"id":2124,"depth":611,"text":2125},"Gate an admin-only surface with JWTAuthMiddleware for authentication and AuthContext.isAllowed for a scope-guarded 401\u002F403\u002F200 ladder on inline routes.","md",null,{},{"icon":65},{"title":445,"description":2184},"uZM5Z0g5EKCpl1OydRtARB-nYxIEy1Dw2y3cwO9bGW0",[2192,2194],{"title":441,"path":442,"stem":443,"description":2193,"icon":154,"children":-1},"Validate a list route's filter\u002Forder\u002Fpage query with QuerySchema.create, and shape its response with PaginatedResponseSchema — capped page size, coerced numeric operands, a total-count envelope.",{"title":449,"path":450,"stem":451,"description":2195,"icon":178,"children":-1},"Expose liveness and readiness over an app-owned controller with an injected HealthRegistry, and run a nightly report on a build-time-validated cron with ScheduledHandler.",1783300761903]