[{"data":1,"prerenderedAt":1701},["ShallowReactive",2],{"navigation_docs_en":3,"-en-auth-rbac":441,"-en-auth-rbac-surround":1696},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":93,"body":443,"description":1689,"extension":1690,"links":1691,"meta":1692,"navigation":1693,"path":94,"seo":1694,"stem":95,"__hash__":1695},"docs_en\u002Fen\u002F12.auth\u002F2.rbac.md",{"type":444,"value":445,"toc":1681},"minimark",[446,455,484,512,556,561,567,657,664,732,747,799,803,812,869,894,897,1083,1098,1102,1128,1363,1369,1427,1448,1452,1479,1500,1507,1511,1537,1630,1639,1643,1677],[447,448,449,450,454],"p",{},"Import the ",[451,452,453],"code",{},"rbac"," subpackage to check roles and permissions over the authenticated identity:",[456,457,462],"pre",{"className":458,"code":459,"language":460,"meta":461,"style":461},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fauth\u002Frbac\"\n","go","",[451,463,464],{"__ignoreMap":461},[465,466,469,473,477,481],"span",{"class":467,"line":468},"line",1,[465,470,472],{"class":471},"s7zQu","import",[465,474,476],{"class":475},"sMK4o"," \"",[465,478,480],{"class":479},"sBMFI","github.com\u002Fgp-system\u002Fauth\u002Frbac",[465,482,483],{"class":475},"\"\n",[447,485,486,488,489,491,492,498,499,505,506,508,509,511],{},[451,487,453],{}," is the ",[451,490,82],{}," module's role-and-permission-checks subpackage: role and permission checks over an authenticated ",[493,494,495],"strong",{},[451,496,497],{},"Identity"," carried in the request context. It takes no position on where roles come from: ",[500,501,502],"a",{"href":89},[451,503,504],{},"auth.Identify"," (or anything else) populates the ",[451,507,497],{},", and ",[451,510,453],{}," only checks: it assumes no database schema and no user table, and it never writes a response.",[447,513,514,515,518,519,522,523,518,526,529,530,533,534,537,538,518,541,544,545,548,549,551,552,555],{},"The package covers three levels: ",[451,516,517],{},"id.HasRole(\"admin\")"," \u002F ",[451,520,521],{},"id.HasPermission(\"orders.view\")"," for the simplest boolean checks, ",[451,524,525],{},"rbac.CheckRole(id, \"admin\")",[451,527,528],{},"rbac.CheckPermission(id, \"orders.view\")"," which return ",[451,531,532],{},"ErrUnauthenticated","\u002F",[451,535,536],{},"ErrForbidden"," instead of a bool (useful when you need to distinguish \"no identity\" from \"wrong role\" without an extra nil check), and the gpsystem kit's ",[451,539,540],{},"server.RequireRole(\"admin\")",[451,542,543],{},"server.RequirePermission(\"orders.view\")"," for route-level ",[451,546,547],{},"net\u002Fhttp"," middleware protection that renders those two sentinels as Problems. See ",[500,550,103],{"href":104}," for a hand-written middleware built on ",[451,553,554],{},"CheckRole"," alone, with no kit involved.",[557,558,560],"h2",{"id":559},"the-identity","The Identity",[447,562,563,564,566],{},"The ",[451,565,497],{}," is the caller as authorization decisions and business logic see it: the contract between authentication and authorization.",[456,568,570],{"className":458,"code":569,"language":460,"meta":461,"style":461},"type Identity struct {\n    Subject     string         \u002F\u002F the user's identifier (the JWT sub claim)\n    Username    string\n    Roles       []string\n    Permissions []string\n    Extra       map[string]any \u002F\u002F claim data beyond the standard fields (e.g. tenant)\n}\n",[451,571,572,586,601,610,621,631,651],{"__ignoreMap":461},[465,573,574,577,580,583],{"class":467,"line":468},[465,575,576],{"class":475},"type",[465,578,579],{"class":479}," Identity",[465,581,582],{"class":475}," struct",[465,584,585],{"class":475}," {\n",[465,587,589,593,597],{"class":467,"line":588},2,[465,590,592],{"class":591},"sTEyZ","    Subject     ",[465,594,596],{"class":595},"spNyl","string",[465,598,600],{"class":599},"sHwdD","         \u002F\u002F the user's identifier (the JWT sub claim)\n",[465,602,604,607],{"class":467,"line":603},3,[465,605,606],{"class":591},"    Username    ",[465,608,609],{"class":595},"string\n",[465,611,613,616,619],{"class":467,"line":612},4,[465,614,615],{"class":591},"    Roles       ",[465,617,618],{"class":475},"[]",[465,620,609],{"class":595},[465,622,624,627,629],{"class":467,"line":623},5,[465,625,626],{"class":591},"    Permissions ",[465,628,618],{"class":475},[465,630,609],{"class":595},[465,632,634,637,640,642,645,648],{"class":467,"line":633},6,[465,635,636],{"class":591},"    Extra       ",[465,638,639],{"class":475},"map[",[465,641,596],{"class":595},[465,643,644],{"class":475},"]",[465,646,647],{"class":479},"any",[465,649,650],{"class":599}," \u002F\u002F claim data beyond the standard fields (e.g. tenant)\n",[465,652,654],{"class":467,"line":653},7,[465,655,656],{"class":475},"}\n",[447,658,659,660,663],{},"It has two query methods, both with ",[493,661,662],{},"any-of"," semantics: one match among several arguments is enough.",[456,665,667],{"className":458,"code":666,"language":460,"meta":461,"style":461},"id.HasRole(\"admin\", \"editor\")       \u002F\u002F true if any of the roles is present\nid.HasPermission(\"orders.view\")     \u002F\u002F true if the permission is present\n",[451,668,669,709],{"__ignoreMap":461},[465,670,671,674,677,681,684,687,691,693,696,698,701,703,706],{"class":467,"line":468},[465,672,673],{"class":591},"id",[465,675,676],{"class":475},".",[465,678,680],{"class":679},"s2Zo4","HasRole",[465,682,683],{"class":475},"(",[465,685,686],{"class":475},"\"",[465,688,690],{"class":689},"sfazB","admin",[465,692,686],{"class":475},[465,694,695],{"class":475},",",[465,697,476],{"class":475},[465,699,700],{"class":689},"editor",[465,702,686],{"class":475},[465,704,705],{"class":475},")",[465,707,708],{"class":599},"       \u002F\u002F true if any of the roles is present\n",[465,710,711,713,715,718,720,722,725,727,729],{"class":467,"line":588},[465,712,673],{"class":591},[465,714,676],{"class":475},[465,716,717],{"class":679},"HasPermission",[465,719,683],{"class":475},[465,721,686],{"class":475},[465,723,724],{"class":689},"orders.view",[465,726,686],{"class":475},[465,728,705],{"class":475},[465,730,731],{"class":599},"     \u002F\u002F true if the permission is present\n",[447,733,734,735,738,739,742,743,746],{},"Both are safe to call ",[493,736,737],{},"on a nil identity"," (they report ",[451,740,741],{},"false","); this matches the fact that ",[451,744,745],{},"FromContext"," returns nil when the request did not pass auth middleware. Your guard code therefore never needs a nil check:",[456,748,750],{"className":458,"code":749,"language":460,"meta":461,"style":461},"if rbac.FromContext(ctx).HasRole(\"admin\") { \u002F\u002F works on nil too, reports false\n    \u002F\u002F ...\n}\n",[451,751,752,790,795],{"__ignoreMap":461},[465,753,754,757,760,762,764,766,769,772,774,776,778,780,782,784,787],{"class":467,"line":468},[465,755,756],{"class":471},"if",[465,758,759],{"class":591}," rbac",[465,761,676],{"class":475},[465,763,745],{"class":679},[465,765,683],{"class":475},[465,767,768],{"class":591},"ctx",[465,770,771],{"class":475},").",[465,773,680],{"class":679},[465,775,683],{"class":475},[465,777,686],{"class":475},[465,779,690],{"class":689},[465,781,686],{"class":475},[465,783,705],{"class":475},[465,785,786],{"class":475}," {",[465,788,789],{"class":599}," \u002F\u002F works on nil too, reports false\n",[465,791,792],{"class":467,"line":588},[465,793,794],{"class":599},"    \u002F\u002F ...\n",[465,796,797],{"class":467,"line":603},[465,798,656],{"class":475},[557,800,802],{"id":801},"withidentity-and-fromcontext","WithIdentity and FromContext",[447,804,563,805,807,808,811],{},[451,806,497],{}," travels in a plain ",[451,809,810],{},"context.Context",":",[456,813,815],{"className":458,"code":814,"language":460,"meta":461,"style":461},"ctx = rbac.WithIdentity(ctx, id) \u002F\u002F called by the auth middleware\nid := rbac.FromContext(ctx)      \u002F\u002F *rbac.Identity, or nil without auth\n",[451,816,817,846],{"__ignoreMap":461},[465,818,819,822,825,827,829,832,834,836,838,841,843],{"class":467,"line":468},[465,820,821],{"class":591},"ctx ",[465,823,824],{"class":475},"=",[465,826,759],{"class":591},[465,828,676],{"class":475},[465,830,831],{"class":679},"WithIdentity",[465,833,683],{"class":475},[465,835,768],{"class":591},[465,837,695],{"class":475},[465,839,840],{"class":591}," id",[465,842,705],{"class":475},[465,844,845],{"class":599}," \u002F\u002F called by the auth middleware\n",[465,847,848,851,854,856,858,860,862,864,866],{"class":467,"line":588},[465,849,850],{"class":591},"id ",[465,852,853],{"class":475},":=",[465,855,759],{"class":591},[465,857,676],{"class":475},[465,859,745],{"class":679},[465,861,683],{"class":475},[465,863,768],{"class":591},[465,865,705],{"class":475},[465,867,868],{"class":599},"      \u002F\u002F *rbac.Identity, or nil without auth\n",[447,870,871,873,874,876,877,879,880,883,884,886,887,876,890,893],{},[451,872,497],{},", ",[451,875,831],{}," and ",[451,878,745],{}," are ",[493,881,882],{},"framework-agnostic",": they don't import ",[451,885,547],{},", or any router. The same contract therefore works outside the HTTP layer too: in services, ",[500,888,889],{"href":68},"listeners",[500,891,892],{"href":78},"jobs",", whenever an identity was put into the context.",[447,895,896],{},"In the shop, for example, order listing narrows to the caller at the service layer: an admin sees everything, a customer only their own.",[456,898,900],{"className":458,"code":899,"language":460,"meta":461,"style":461},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fservice\u002Forders.go\nfunc (s *Service) ListOrders(ctx context.Context, cursor string) ([]Order, error) {\n    id := rbac.FromContext(ctx)\n    if id.HasRole(\"admin\") {\n        return s.orders.ListAll(ctx, cursor)\n    }\n    return s.orders.ListByUser(ctx, id.Subject, cursor)\n}\n",[451,901,902,907,967,987,1010,1038,1043,1078],{"__ignoreMap":461},[465,903,904],{"class":467,"line":468},[465,905,906],{"class":599},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fservice\u002Forders.go\n",[465,908,909,912,915,919,922,925,927,930,932,934,937,939,942,944,947,950,952,955,958,960,963,965],{"class":467,"line":588},[465,910,911],{"class":475},"func",[465,913,914],{"class":475}," (",[465,916,918],{"class":917},"sHdIc","s ",[465,920,921],{"class":475},"*",[465,923,924],{"class":479},"Service",[465,926,705],{"class":475},[465,928,929],{"class":679}," ListOrders",[465,931,683],{"class":475},[465,933,768],{"class":917},[465,935,936],{"class":479}," context",[465,938,676],{"class":475},[465,940,941],{"class":479},"Context",[465,943,695],{"class":475},[465,945,946],{"class":917}," cursor",[465,948,949],{"class":595}," string",[465,951,705],{"class":475},[465,953,954],{"class":475}," ([]",[465,956,957],{"class":479},"Order",[465,959,695],{"class":475},[465,961,962],{"class":595}," error",[465,964,705],{"class":475},[465,966,585],{"class":475},[465,968,969,972,974,976,978,980,982,984],{"class":467,"line":603},[465,970,971],{"class":591},"    id ",[465,973,853],{"class":475},[465,975,759],{"class":591},[465,977,676],{"class":475},[465,979,745],{"class":679},[465,981,683],{"class":475},[465,983,768],{"class":591},[465,985,986],{"class":475},")\n",[465,988,989,992,994,996,998,1000,1002,1004,1006,1008],{"class":467,"line":612},[465,990,991],{"class":471},"    if",[465,993,840],{"class":591},[465,995,676],{"class":475},[465,997,680],{"class":679},[465,999,683],{"class":475},[465,1001,686],{"class":475},[465,1003,690],{"class":689},[465,1005,686],{"class":475},[465,1007,705],{"class":475},[465,1009,585],{"class":475},[465,1011,1012,1015,1018,1020,1023,1025,1028,1030,1032,1034,1036],{"class":467,"line":623},[465,1013,1014],{"class":471},"        return",[465,1016,1017],{"class":591}," s",[465,1019,676],{"class":475},[465,1021,1022],{"class":591},"orders",[465,1024,676],{"class":475},[465,1026,1027],{"class":679},"ListAll",[465,1029,683],{"class":475},[465,1031,768],{"class":591},[465,1033,695],{"class":475},[465,1035,946],{"class":591},[465,1037,986],{"class":475},[465,1039,1040],{"class":467,"line":633},[465,1041,1042],{"class":475},"    }\n",[465,1044,1045,1048,1050,1052,1054,1056,1059,1061,1063,1065,1067,1069,1072,1074,1076],{"class":467,"line":653},[465,1046,1047],{"class":471},"    return",[465,1049,1017],{"class":591},[465,1051,676],{"class":475},[465,1053,1022],{"class":591},[465,1055,676],{"class":475},[465,1057,1058],{"class":679},"ListByUser",[465,1060,683],{"class":475},[465,1062,768],{"class":591},[465,1064,695],{"class":475},[465,1066,840],{"class":591},[465,1068,676],{"class":475},[465,1070,1071],{"class":591},"Subject",[465,1073,695],{"class":475},[465,1075,946],{"class":591},[465,1077,986],{"class":475},[465,1079,1081],{"class":467,"line":1080},8,[465,1082,656],{"class":475},[447,1084,1085,1086,1089,1090,1094,1095,676],{},"This is ",[493,1087,1088],{},"data scoping"," (what to show), not an access decision (whether it is allowed at all). Per-request authorization, meaning \"may they view ",[1091,1092,1093],"em",{},"this particular"," order\", is the job of ",[500,1096,1097],{"href":99},"policies",[557,1099,1101],{"id":1100},"route-guards","Route guards",[447,1103,1104,1105,518,1108,1111,1112,1117,1118,1120,1121,533,1124,1127],{},"The kit's ",[451,1106,1107],{},"server.RequireRole",[451,1109,1110],{},"server.RequirePermission"," middlewares protect a whole route group. They assume ",[500,1113,1114],{"href":89},[451,1115,1116],{},"server.AuthMiddleware"," already ran before them; they only inspect the ",[451,1119,497],{}," in the context, via ",[451,1122,1123],{},"rbac.CheckRole",[451,1125,1126],{},"CheckPermission",", and render the result as a Problem.",[456,1129,1131],{"className":458,"code":1130,"language":460,"meta":461,"style":461},"router.Route(\"\u002Fadmin\u002Fshop\", func(r chi.Router) {\n    r.Use(\n        server.AuthMiddleware(deps.Auth), \u002F\u002F Bearer → parse → Identity into context\n        server.RequireRole(\"admin\"),      \u002F\u002F 401 without identity, 403 without the role\n    )\n    \u002F\u002F ...\n})\n\n\u002F\u002F permission-based variant, with any-of semantics:\nrouter.Route(\"\u002Freports\", func(r chi.Router) {\n    r.Use(server.AuthMiddleware(deps.Auth),\n        server.RequirePermission(\"reports.view\", \"reports.export\"))\n    \u002F\u002F ...\n})\n",[451,1132,1133,1172,1185,1211,1233,1238,1242,1247,1253,1259,1293,1322,1353,1358],{"__ignoreMap":461},[465,1134,1135,1138,1140,1143,1145,1147,1150,1152,1154,1157,1160,1163,1165,1168,1170],{"class":467,"line":468},[465,1136,1137],{"class":591},"router",[465,1139,676],{"class":475},[465,1141,1142],{"class":679},"Route",[465,1144,683],{"class":475},[465,1146,686],{"class":475},[465,1148,1149],{"class":689},"\u002Fadmin\u002Fshop",[465,1151,686],{"class":475},[465,1153,695],{"class":475},[465,1155,1156],{"class":475}," func(",[465,1158,1159],{"class":917},"r",[465,1161,1162],{"class":479}," chi",[465,1164,676],{"class":475},[465,1166,1167],{"class":479},"Router",[465,1169,705],{"class":475},[465,1171,585],{"class":475},[465,1173,1174,1177,1179,1182],{"class":467,"line":588},[465,1175,1176],{"class":591},"    r",[465,1178,676],{"class":475},[465,1180,1181],{"class":679},"Use",[465,1183,1184],{"class":475},"(\n",[465,1186,1187,1190,1192,1195,1197,1200,1202,1205,1208],{"class":467,"line":603},[465,1188,1189],{"class":591},"        server",[465,1191,676],{"class":475},[465,1193,1194],{"class":679},"AuthMiddleware",[465,1196,683],{"class":475},[465,1198,1199],{"class":591},"deps",[465,1201,676],{"class":475},[465,1203,1204],{"class":591},"Auth",[465,1206,1207],{"class":475},"),",[465,1209,1210],{"class":599}," \u002F\u002F Bearer → parse → Identity into context\n",[465,1212,1213,1215,1217,1220,1222,1224,1226,1228,1230],{"class":467,"line":612},[465,1214,1189],{"class":591},[465,1216,676],{"class":475},[465,1218,1219],{"class":679},"RequireRole",[465,1221,683],{"class":475},[465,1223,686],{"class":475},[465,1225,690],{"class":689},[465,1227,686],{"class":475},[465,1229,1207],{"class":475},[465,1231,1232],{"class":599},"      \u002F\u002F 401 without identity, 403 without the role\n",[465,1234,1235],{"class":467,"line":623},[465,1236,1237],{"class":475},"    )\n",[465,1239,1240],{"class":467,"line":633},[465,1241,794],{"class":599},[465,1243,1244],{"class":467,"line":653},[465,1245,1246],{"class":475},"})\n",[465,1248,1249],{"class":467,"line":1080},[465,1250,1252],{"emptyLinePlaceholder":1251},true,"\n",[465,1254,1256],{"class":467,"line":1255},9,[465,1257,1258],{"class":599},"\u002F\u002F permission-based variant, with any-of semantics:\n",[465,1260,1262,1264,1266,1268,1270,1272,1275,1277,1279,1281,1283,1285,1287,1289,1291],{"class":467,"line":1261},10,[465,1263,1137],{"class":591},[465,1265,676],{"class":475},[465,1267,1142],{"class":679},[465,1269,683],{"class":475},[465,1271,686],{"class":475},[465,1273,1274],{"class":689},"\u002Freports",[465,1276,686],{"class":475},[465,1278,695],{"class":475},[465,1280,1156],{"class":475},[465,1282,1159],{"class":917},[465,1284,1162],{"class":479},[465,1286,676],{"class":475},[465,1288,1167],{"class":479},[465,1290,705],{"class":475},[465,1292,585],{"class":475},[465,1294,1296,1298,1300,1302,1304,1307,1309,1311,1313,1315,1317,1319],{"class":467,"line":1295},11,[465,1297,1176],{"class":591},[465,1299,676],{"class":475},[465,1301,1181],{"class":679},[465,1303,683],{"class":475},[465,1305,1306],{"class":591},"server",[465,1308,676],{"class":475},[465,1310,1194],{"class":679},[465,1312,683],{"class":475},[465,1314,1199],{"class":591},[465,1316,676],{"class":475},[465,1318,1204],{"class":591},[465,1320,1321],{"class":475},"),\n",[465,1323,1325,1327,1329,1332,1334,1336,1339,1341,1343,1345,1348,1350],{"class":467,"line":1324},12,[465,1326,1189],{"class":591},[465,1328,676],{"class":475},[465,1330,1331],{"class":679},"RequirePermission",[465,1333,683],{"class":475},[465,1335,686],{"class":475},[465,1337,1338],{"class":689},"reports.view",[465,1340,686],{"class":475},[465,1342,695],{"class":475},[465,1344,476],{"class":475},[465,1346,1347],{"class":689},"reports.export",[465,1349,686],{"class":475},[465,1351,1352],{"class":475},"))\n",[465,1354,1356],{"class":467,"line":1355},13,[465,1357,794],{"class":599},[465,1359,1361],{"class":467,"line":1360},14,[465,1362,1246],{"class":475},[447,1364,1365,1366,811],{},"The responses are ",[500,1367,1368],{"href":395},"RFC 9457 problem documents",[1370,1371,1372,1385],"table",{},[1373,1374,1375],"thead",{},[1376,1377,1378,1382],"tr",{},[1379,1380,1381],"th",{},"Situation",[1379,1383,1384],{},"Result",[1386,1387,1388,1406,1419],"tbody",{},[1376,1389,1390,1397],{},[1391,1392,1393,1394,1396],"td",{},"no ",[451,1395,497],{}," in the context (auth did not run)",[1391,1398,1399,1402,1403],{},[493,1400,1401],{},"401"," ",[451,1404,1405],{},"authentication required",[1376,1407,1408,1411],{},[1391,1409,1410],{},"identity present, but none of the listed roles\u002Fpermissions held",[1391,1412,1413,1402,1416],{},[493,1414,1415],{},"403",[451,1417,1418],{},"insufficient privileges",[1376,1420,1421,1424],{},[1391,1422,1423],{},"any of the listed roles\u002Fpermissions held (any-of)",[1391,1425,1426],{},"pass",[447,1428,1429,1430,533,1432,1434,1435,533,1437,1439,1440,533,1443,1445,1446,676],{},"Underneath, ",[451,1431,1107],{},[451,1433,1331],{}," call ",[451,1436,1123],{},[451,1438,1126],{}," directly and map ",[451,1441,1442],{},"rbac.ErrUnauthenticated",[451,1444,536],{}," to those two statuses; a caller with no kit does the same mapping by hand, as shown in ",[500,1447,103],{"href":104},[557,1449,1451],{"id":1450},"with-the-kit","With the kit",[447,1453,1454,1455,1457,1458,1461,1462,1464,1465,1468,1469,1472,1473,1476,1477,676],{},"In the shop, the entire ",[451,1456,690],{}," surface (product CRUD, image upload) sits behind the pair above: the middleware goes into the ",[451,1459,1460],{},"RegisterAdmin"," function generated by ",[451,1463,329],{},", so the protection is surface-wide and the sibling ",[451,1466,1467],{},"api"," surface is untouched. The exact wiring (and adding the ",[451,1470,1471],{},"deps.Auth"," field) is shown on the ",[500,1474,1475],{"href":89},"authentication"," page; the surface-per-function structure is covered by ",[500,1478,329],{"href":330},[447,1480,1481,1482,1484,1485,1489,1490,1492,1493,533,1496,1499],{},"The same ",[451,1483,497],{}," also shows up outside the HTTP request\u002Fresponse cycle: the ",[500,1486,1488],{"href":1487},"\u002Fen\u002Fkit\u002Frealtime#the-gateway","realtime gateway"," resolves each WebSocket connection's JWT into an ",[451,1491,497],{}," and checks it against the ",[451,1494,1495],{},"Allow",[451,1497,1498],{},"AllowPrefix"," callbacks you register for a topic, so a connection only ever subscribes to channels the caller is authorized for.",[447,1501,1502,1503,1506],{},"For finer granularity the same guards can go on a smaller group instead of the whole surface, but once the decision is no longer \"do they have this role\" but \"do they own this resource\", it is not RBAC anymore: that is what the ",[500,1504,1505],{"href":99},"policy layer"," is for.",[557,1508,1510],{"id":1509},"where-roles-and-permissions-come-from","Where roles and permissions come from",[447,1512,1513,1514,1517,1518,1520,1521,1523,1524,1526,1527,1529,1530,533,1533,1536],{},"In the kit's default setup, from the JWT claims: the login endpoint writes the user's roles and permissions into ",[451,1515,1516],{},"DefaultClaims",", and the auth middleware fills the ",[451,1519,497],{}," fields from them. The full chain is on the ",[500,1522,1475],{"href":89}," page. Since ",[451,1525,453],{}," only ever sees the ",[451,1528,497],{},", the source is swappable: a custom claims type (",[451,1531,1532],{},"Identify",[451,1534,1535],{},"server.AuthMiddlewareFor","), an identity built from an API key, or a test fixture all work the same way.",[1538,1539,1540,1547],"tip",{},[447,1541,1542,1543,1546],{},"Tests need no HTTP: with ",[451,1544,1545],{},"rbac.WithIdentity"," you can put the desired identity directly into the context, and the service layer behaves as if the middleware had populated it.",[456,1548,1550],{"className":458,"code":1549,"language":460,"meta":461,"style":461},"ctx := rbac.WithIdentity(t.Context(), &rbac.Identity{\n    Subject: \"u-42\", Roles: []string{\"admin\"},\n})\n",[451,1551,1552,1588,1626],{"__ignoreMap":461},[465,1553,1554,1556,1558,1560,1562,1564,1566,1569,1571,1573,1576,1579,1581,1583,1585],{"class":467,"line":468},[465,1555,821],{"class":591},[465,1557,853],{"class":475},[465,1559,759],{"class":591},[465,1561,676],{"class":475},[465,1563,831],{"class":679},[465,1565,683],{"class":475},[465,1567,1568],{"class":591},"t",[465,1570,676],{"class":475},[465,1572,941],{"class":679},[465,1574,1575],{"class":475},"(),",[465,1577,1578],{"class":475}," &",[465,1580,453],{"class":479},[465,1582,676],{"class":475},[465,1584,497],{"class":479},[465,1586,1587],{"class":475},"{\n",[465,1589,1590,1593,1595,1597,1600,1602,1604,1607,1609,1612,1614,1617,1619,1621,1623],{"class":467,"line":588},[465,1591,1592],{"class":591},"    Subject",[465,1594,811],{"class":475},[465,1596,476],{"class":475},[465,1598,1599],{"class":689},"u-42",[465,1601,686],{"class":475},[465,1603,695],{"class":475},[465,1605,1606],{"class":591}," Roles",[465,1608,811],{"class":475},[465,1610,1611],{"class":475}," []",[465,1613,596],{"class":595},[465,1615,1616],{"class":475},"{",[465,1618,686],{"class":475},[465,1620,690],{"class":689},[465,1622,686],{"class":475},[465,1624,1625],{"class":475},"},\n",[465,1627,1628],{"class":467,"line":603},[465,1629,1246],{"class":475},[447,1631,1632,1633,1636,1637,676],{},"A role says what the caller may do ",[1091,1634,1635],{},"in general",". When the decision also needs the concrete request (are they the owner, are they in the same tenant, is the record in the right state), move on to ",[500,1638,1097],{"href":99},[557,1640,1642],{"id":1641},"patterns-used","Patterns used",[1644,1645,1646,1660],"ul",{},[1647,1648,1649,914,1652,533,1654,1656,1657,676],"li",{},[493,1650,1651],{},"Context-injected identity",[451,1653,831],{},[451,1655,745],{},", unexported key, nil-safe methods): ",[500,1658,238],{"href":1659},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#context-injected-dependency",[1647,1661,1662,914,1665,1668,1669,876,1671,1673,1674,676],{},[493,1663,1664],{},"Higher-order authorization guard",[451,1666,1667],{},"requireFn",", shared by both ",[451,1670,1219],{},[451,1672,1331],{},"): ",[500,1675,238],{"href":1676},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#higher-order-authorization-guard",[1678,1679,1680],"style",{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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}",{"title":461,"searchDepth":588,"depth":588,"links":1682},[1683,1684,1685,1686,1687,1688],{"id":559,"depth":588,"text":560},{"id":801,"depth":588,"text":802},{"id":1100,"depth":588,"text":1101},{"id":1450,"depth":588,"text":1451},{"id":1509,"depth":588,"text":1510},{"id":1641,"depth":588,"text":1642},"Role and permission checks over the Identity carried in the context: with route guards and service-layer queries.","md",null,{},{"icon":96},{"title":93,"description":1689},"yJ8QNS9sQ3HOsfPAZIjccwFMTt48it_swfRZvl9JI1c",[1697,1699],{"title":88,"path":89,"stem":90,"description":1698,"icon":91,"children":-1},"JWT issuing and a transport-agnostic Identify function: stateless auth with no framework baked in.",{"title":98,"path":99,"stem":100,"description":1700,"icon":101,"children":-1},"Per-request, per-user authorization above roles: declared in the contract, enforced from generated code.",1785445891444]