[{"data":1,"prerenderedAt":2938},["ShallowReactive",2],{"navigation_docs_en":3,"-en-auth-overview":441,"-en-auth-overview-surround":2933},[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":88,"body":443,"description":2926,"extension":2927,"links":2928,"meta":2929,"navigation":2930,"path":89,"seo":2931,"stem":90,"__hash__":2932},"docs_en\u002Fen\u002F12.auth\u002F1.overview.md",{"type":444,"value":445,"toc":2913},"minimark",[446,454,483,537,571,578,593,597,625,652,655,669,761,866,897,901,914,994,1020,1765,1782,1786,1805,1970,1988,1993,2017,2224,2275,2279,2299,2505,2508,2526,2782,2808,2812,2830,2844,2848,2870,2890,2894,2909],[447,448,449,450,453],"p",{},"Import ",[451,452,82],"code",{}," to issue JWTs and turn a bearer token into an identity:",[455,456,461],"pre",{"className":457,"code":458,"language":459,"meta":460,"style":460},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fauth\"\n","go","",[451,462,463],{"__ignoreMap":460},[464,465,468,472,476,480],"span",{"class":466,"line":467},"line",1,[464,469,471],{"class":470},"s7zQu","import",[464,473,475],{"class":474},"sMK4o"," \"",[464,477,479],{"class":478},"sBMFI","github.com\u002Fgp-system\u002Fauth",[464,481,482],{"class":474},"\"\n",[447,484,485,487,488,492,493,498,499,503,504,510,511,514,515,518,519,524,525,528,529,532,533,536],{},[451,486,82],{}," is a standalone, stateless JWT module with ",[489,490,491],"strong",{},"no dependency on any other gp-system package",", not even ",[494,495,496],"a",{"href":362},[451,497,357],{}," or ",[494,500,501],{"href":391},[451,502,385],{},": its only dependency is ",[494,505,509],{"href":506,"rel":507},"https:\u002F\u002Fgithub.com\u002Fgolang-jwt\u002Fjwt",[508],"nofollow","golang-jwt\u002Fjwt\u002Fv5",". It provides two things: a ",[489,512,513],{},"token issuer"," (JWT signing and verification) and ",[451,516,517],{},"Identify",", a function that turns a bearer token into an ",[494,520,521],{"href":94},[451,522,523],{},"rbac.Identity",". From there on, every authorization decision (the ",[494,526,527],{"href":94},"role and permission checks",", the ",[494,530,531],{"href":99},"policies",") happens over the ",[451,534,535],{},"Identity",", independent of the token.",[538,539,540,542,543,545,546,549,550,553,554,557,558,560,561,563,564,566,567,570],"note",{},[451,541,82],{}," never writes an HTTP response, and ",[451,544,517],{}," doesn't even take a ",[451,547,548],{},"*http.Request",": it takes a header ",[489,551,552],{},"value",". That makes it usable from any transport, not just ",[451,555,556],{},"net\u002Fhttp",": a WebSocket handshake, a gRPC interceptor reading metadata, a message-queue consumer checking a token in a message header. Every failure is a plain Go error (a sentinel or a small typed value); the caller decides how to render it. See ",[494,559,103],{"href":104}," for worked examples with and without ",[451,562,556],{},". The gpsystem kit adds a ",[451,565,556],{}," middleware and RFC 9457 Problem rendering on top, in its ",[451,568,569],{},"server"," package.",[447,572,573,574,577],{},"Authentication here is built around a ",[489,575,576],{},"stateless JWT",": the login endpoint hands out a signed token, every subsequent request carries it, and the server keeps no session store: the token itself carries the subject, roles and permissions.",[538,579,580,581,584,585,588,589,592],{},"The package builds on ",[494,582,509],{"href":506,"rel":583},[508]," and does not hide it: claims types implement the ",[451,586,587],{},"jwt.Claims"," interface, and a custom claims struct embeds ",[451,590,591],{},"jwt.RegisteredClaims",". The module centralizes here rather than abstracting: your JWT-ecosystem knowledge transfers one to one.",[594,595,25],"h2",{"id":596},"installation",[455,598,602],{"className":599,"code":600,"language":601,"meta":460,"style":460},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com\u002Fgp-system\u002Fauth@v0.1.0   # the kit uses this same tag\n# or: go get github.com\u002Fgp-system\u002Fauth@latest\n","sh",[451,603,604,619],{"__ignoreMap":460},[464,605,606,608,612,615],{"class":466,"line":467},[464,607,459],{"class":478},[464,609,611],{"class":610},"sfazB"," get",[464,613,614],{"class":610}," github.com\u002Fgp-system\u002Fauth@v0.1.0",[464,616,618],{"class":617},"sHwdD","   # the kit uses this same tag\n",[464,620,622],{"class":466,"line":621},2,[464,623,624],{"class":617},"# or: go get github.com\u002Fgp-system\u002Fauth@latest\n",[447,626,627,629,630,633,634,637,638,641,642,645,646,648,649,651],{},[451,628,82],{},"'s only dependency is ",[494,631,509],{"href":506,"rel":632},[508],"; the ",[451,635,636],{},"auth\u002Frbac"," and ",[451,639,640],{},"auth\u002Fpolicy"," subpackages ship inside the same module, no extra ",[451,643,644],{},"go get",". Nothing here requires the gpsystem kit or any other gp-system module: see ",[494,647,103],{"href":104}," for a project that imports only ",[451,650,479],{},".",[594,653,204],{"id":654},"configuration",[447,656,657,660,661,664,665,668],{},[451,658,659],{},"auth.Config"," loads from the environment; in a generated project's config you add it under the ",[451,662,663],{},"\u002F\u002F gpsystem:config"," anchor with the ",[451,666,667],{},"JWT_"," prefix:",[455,670,672],{"className":457,"code":671,"language":459,"meta":460,"style":460},"\u002F\u002F internal\u002Fplatform\u002Fconfig\u002Fconfig.go\ntype Config struct {\n    Server server.Config\n    DB     dbx.Config  `envPrefix:\"DB_\"`\n    JWT    auth.Config `envPrefix:\"JWT_\"`\n    \u002F\u002F gpsystem:config\n}\n",[451,673,674,679,693,707,729,749,755],{"__ignoreMap":460},[464,675,676],{"class":466,"line":467},[464,677,678],{"class":617},"\u002F\u002F internal\u002Fplatform\u002Fconfig\u002Fconfig.go\n",[464,680,681,684,687,690],{"class":466,"line":621},[464,682,683],{"class":474},"type",[464,685,686],{"class":478}," Config",[464,688,689],{"class":474}," struct",[464,691,692],{"class":474}," {\n",[464,694,696,700,702,704],{"class":466,"line":695},3,[464,697,699],{"class":698},"sTEyZ","    Server ",[464,701,569],{"class":478},[464,703,651],{"class":474},[464,705,706],{"class":478},"Config\n",[464,708,710,713,715,717,720,723,726],{"class":466,"line":709},4,[464,711,712],{"class":698},"    DB     ",[464,714,404],{"class":478},[464,716,651],{"class":474},[464,718,719],{"class":478},"Config",[464,721,722],{"class":474},"  `",[464,724,725],{"class":610},"envPrefix:\"DB_\"",[464,727,728],{"class":474},"`\n",[464,730,732,735,737,739,741,744,747],{"class":466,"line":731},5,[464,733,734],{"class":698},"    JWT    ",[464,736,82],{"class":478},[464,738,651],{"class":474},[464,740,719],{"class":478},[464,742,743],{"class":474}," `",[464,745,746],{"class":610},"envPrefix:\"JWT_\"",[464,748,728],{"class":474},[464,750,752],{"class":466,"line":751},6,[464,753,754],{"class":617},"    \u002F\u002F gpsystem:config\n",[464,756,758],{"class":466,"line":757},7,[464,759,760],{"class":474},"}\n",[762,763,764,780],"table",{},[765,766,767],"thead",{},[768,769,770,774,777],"tr",{},[771,772,773],"th",{},"Env variable",[771,775,776],{},"Field",[771,778,779],{},"Default",[781,782,783,801,818,836,851],"tbody",{},[768,784,785,791,796],{},[786,787,788],"td",{},[451,789,790],{},"JWT_SECRET",[786,792,793],{},[451,794,795],{},"Secret",[786,797,798],{},[489,799,800],{},"required",[768,802,803,808,813],{},[786,804,805],{},[451,806,807],{},"JWT_ACCESS_TOKEN_TTL",[786,809,810],{},[451,811,812],{},"AccessTTL",[786,814,815],{},[451,816,817],{},"15m",[768,819,820,825,830],{},[786,821,822],{},[451,823,824],{},"JWT_REFRESH_TOKEN_TTL",[786,826,827],{},[451,828,829],{},"RefreshTTL",[786,831,832,835],{},[451,833,834],{},"168h"," (7 days)",[768,837,838,843,848],{},[786,839,840],{},[451,841,842],{},"JWT_ISSUER",[786,844,845],{},[451,846,847],{},"Issuer",[786,849,850],{},"empty (issuer check disabled)",[768,852,853,858,863],{},[786,854,855],{},[451,856,857],{},"JWT_AUDIENCE",[786,859,860],{},[451,861,862],{},"Audience",[786,864,865],{},"empty (audience check disabled)",[867,868,869,870,637,872,874,875,881,882,884,885,887,888,884,891,894,895,651],"warning",{},"Set ",[451,871,847],{},[451,873,862],{},", and use a ",[489,876,877,878,880],{},"distinct ",[451,879,795],{}," per service and per environment",": that way a token minted for another service or for staging cannot be replayed against the production API. When ",[451,883,847],{},"\u002F",[451,886,862],{}," are set, parsing requires a matching ",[451,889,890],{},"iss",[451,892,893],{},"aud"," claim. Full env reference: ",[494,896,204],{"href":205},[594,898,900],{"id":899},"issuing-tokens-the-login-flow","Issuing tokens: the login flow",[447,902,903,904,907,908,910,911,913],{},"The kit's standard claims shape is ",[451,905,906],{},"DefaultClaims",": compatible with the ",[451,909,523],{}," fields, embedding ",[451,912,591],{},":",[455,915,917],{"className":457,"code":916,"language":459,"meta":460,"style":460},"type DefaultClaims struct {\n    Username    string   `json:\"username,omitempty\"`\n    Roles       []string `json:\"roles,omitempty\"`\n    Permissions []string `json:\"permissions,omitempty\"`\n    jwt.RegisteredClaims\n}\n",[451,918,919,930,947,964,980,990],{"__ignoreMap":460},[464,920,921,923,926,928],{"class":466,"line":467},[464,922,683],{"class":474},[464,924,925],{"class":478}," DefaultClaims",[464,927,689],{"class":474},[464,929,692],{"class":474},[464,931,932,935,939,942,945],{"class":466,"line":621},[464,933,934],{"class":698},"    Username    ",[464,936,938],{"class":937},"spNyl","string",[464,940,941],{"class":474},"   `",[464,943,944],{"class":610},"json:\"username,omitempty\"",[464,946,728],{"class":474},[464,948,949,952,955,957,959,962],{"class":466,"line":695},[464,950,951],{"class":698},"    Roles       ",[464,953,954],{"class":474},"[]",[464,956,938],{"class":937},[464,958,743],{"class":474},[464,960,961],{"class":610},"json:\"roles,omitempty\"",[464,963,728],{"class":474},[464,965,966,969,971,973,975,978],{"class":466,"line":709},[464,967,968],{"class":698},"    Permissions ",[464,970,954],{"class":474},[464,972,938],{"class":937},[464,974,743],{"class":474},[464,976,977],{"class":610},"json:\"permissions,omitempty\"",[464,979,728],{"class":474},[464,981,982,985,987],{"class":466,"line":731},[464,983,984],{"class":478},"    jwt",[464,986,651],{"class":474},[464,988,989],{"class":478},"RegisteredClaims\n",[464,991,992],{"class":466,"line":751},[464,993,760],{"class":474},[447,995,996,999,1000,1003,1004,1003,1007,1003,1009,1012,1013,1015,1016,1019],{},[451,997,998],{},"NewDefaultClaims"," stamps the registered claims (",[451,1001,1002],{},"iat",", ",[451,1005,1006],{},"exp",[451,1008,890],{},[451,1010,1011],{},"sub",", plus ",[451,1014,893],{}," when configured) from the config; the ",[451,1017,1018],{},"TokenIssuer"," signs and parses the token. This is what the shop's login service looks like, sketched:",[455,1021,1023],{"className":457,"code":1022,"language":459,"meta":460,"style":460},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fadmin\u002Fservice\u002Fauth.go (sketch)\ntype Service struct {\n    jwt    auth.Config\n    issuer *auth.TokenIssuer[auth.DefaultClaims]\n    users  UserRepo\n    tokens RefreshTokenRepo\n}\n\nfunc New(jwtCfg auth.Config, users UserRepo, tokens RefreshTokenRepo) *Service {\n    return &Service{\n        jwt:    jwtCfg,\n        issuer: auth.NewTokenIssuer[auth.DefaultClaims](jwtCfg),\n        users:  users,\n        tokens: tokens,\n    }\n}\n\nfunc (s *Service) Login(ctx context.Context, email, password string) (LoginResult, error) {\n    user, err := s.users.FindByEmail(ctx, email)\n    if err != nil || !user.CheckPassword(password) {\n        return LoginResult{}, ErrInvalidCredentials\n    }\n\n    claims := auth.NewDefaultClaims(s.jwt, user.ID, user.Name,\n        user.Roles, user.Permissions) \u002F\u002F iat\u002Fexp\u002Fiss\u002Fsub stamped from config\n    access, err := s.issuer.Sign(claims)\n    if err != nil {\n        return LoginResult{}, err\n    }\n\n    refresh, err := auth.NewRefreshToken() \u002F\u002F 32 bytes crypto\u002Frand, hex\n    if err != nil {\n        return LoginResult{}, err\n    }\n    expiresAt := time.Now().Add(s.jwt.RefreshTTL)\n    if err := s.tokens.Store(ctx, user.ID, sha256Hex(refresh), expiresAt); err != nil {\n        return LoginResult{}, err\n    }\n\n    return LoginResult{AccessToken: access, RefreshToken: refresh}, nil\n}\n",[451,1024,1025,1030,1041,1052,1078,1086,1094,1098,1105,1157,1171,1185,1216,1229,1241,1247,1252,1257,1319,1357,1394,1409,1414,1419,1464,1489,1520,1533,1545,1550,1555,1580,1593,1604,1609,1645,1705,1716,1721,1726,1760],{"__ignoreMap":460},[464,1026,1027],{"class":466,"line":467},[464,1028,1029],{"class":617},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fadmin\u002Fservice\u002Fauth.go (sketch)\n",[464,1031,1032,1034,1037,1039],{"class":466,"line":621},[464,1033,683],{"class":474},[464,1035,1036],{"class":478}," Service",[464,1038,689],{"class":474},[464,1040,692],{"class":474},[464,1042,1043,1046,1048,1050],{"class":466,"line":695},[464,1044,1045],{"class":698},"    jwt    ",[464,1047,82],{"class":478},[464,1049,651],{"class":474},[464,1051,706],{"class":478},[464,1053,1054,1057,1060,1062,1064,1066,1069,1071,1073,1075],{"class":466,"line":709},[464,1055,1056],{"class":698},"    issuer ",[464,1058,1059],{"class":474},"*",[464,1061,82],{"class":478},[464,1063,651],{"class":474},[464,1065,1018],{"class":478},[464,1067,1068],{"class":474},"[",[464,1070,82],{"class":478},[464,1072,651],{"class":474},[464,1074,906],{"class":478},[464,1076,1077],{"class":474},"]\n",[464,1079,1080,1083],{"class":466,"line":731},[464,1081,1082],{"class":698},"    users  ",[464,1084,1085],{"class":478},"UserRepo\n",[464,1087,1088,1091],{"class":466,"line":751},[464,1089,1090],{"class":698},"    tokens ",[464,1092,1093],{"class":478},"RefreshTokenRepo\n",[464,1095,1096],{"class":466,"line":757},[464,1097,760],{"class":474},[464,1099,1101],{"class":466,"line":1100},8,[464,1102,1104],{"emptyLinePlaceholder":1103},true,"\n",[464,1106,1108,1111,1115,1118,1122,1125,1127,1129,1132,1135,1138,1140,1143,1146,1149,1152,1155],{"class":466,"line":1107},9,[464,1109,1110],{"class":474},"func",[464,1112,1114],{"class":1113},"s2Zo4"," New",[464,1116,1117],{"class":474},"(",[464,1119,1121],{"class":1120},"sHdIc","jwtCfg",[464,1123,1124],{"class":478}," auth",[464,1126,651],{"class":474},[464,1128,719],{"class":478},[464,1130,1131],{"class":474},",",[464,1133,1134],{"class":1120}," users",[464,1136,1137],{"class":478}," UserRepo",[464,1139,1131],{"class":474},[464,1141,1142],{"class":1120}," tokens",[464,1144,1145],{"class":478}," RefreshTokenRepo",[464,1147,1148],{"class":474},")",[464,1150,1151],{"class":474}," *",[464,1153,1154],{"class":478},"Service",[464,1156,692],{"class":474},[464,1158,1160,1163,1166,1168],{"class":466,"line":1159},10,[464,1161,1162],{"class":470},"    return",[464,1164,1165],{"class":474}," &",[464,1167,1154],{"class":478},[464,1169,1170],{"class":474},"{\n",[464,1172,1174,1177,1179,1182],{"class":466,"line":1173},11,[464,1175,1176],{"class":698},"        jwt",[464,1178,913],{"class":474},[464,1180,1181],{"class":698},"    jwtCfg",[464,1183,1184],{"class":474},",\n",[464,1186,1188,1191,1193,1195,1197,1200,1202,1204,1206,1208,1211,1213],{"class":466,"line":1187},12,[464,1189,1190],{"class":698},"        issuer",[464,1192,913],{"class":474},[464,1194,1124],{"class":698},[464,1196,651],{"class":474},[464,1198,1199],{"class":1113},"NewTokenIssuer",[464,1201,1068],{"class":474},[464,1203,82],{"class":478},[464,1205,651],{"class":474},[464,1207,906],{"class":478},[464,1209,1210],{"class":474},"](",[464,1212,1121],{"class":698},[464,1214,1215],{"class":474},"),\n",[464,1217,1219,1222,1224,1227],{"class":466,"line":1218},13,[464,1220,1221],{"class":698},"        users",[464,1223,913],{"class":474},[464,1225,1226],{"class":698},"  users",[464,1228,1184],{"class":474},[464,1230,1232,1235,1237,1239],{"class":466,"line":1231},14,[464,1233,1234],{"class":698},"        tokens",[464,1236,913],{"class":474},[464,1238,1142],{"class":698},[464,1240,1184],{"class":474},[464,1242,1244],{"class":466,"line":1243},15,[464,1245,1246],{"class":474},"    }\n",[464,1248,1250],{"class":466,"line":1249},16,[464,1251,760],{"class":474},[464,1253,1255],{"class":466,"line":1254},17,[464,1256,1104],{"emptyLinePlaceholder":1103},[464,1258,1260,1262,1265,1268,1270,1272,1274,1277,1279,1282,1285,1287,1290,1292,1295,1297,1300,1303,1305,1307,1310,1312,1315,1317],{"class":466,"line":1259},18,[464,1261,1110],{"class":474},[464,1263,1264],{"class":474}," (",[464,1266,1267],{"class":1120},"s ",[464,1269,1059],{"class":474},[464,1271,1154],{"class":478},[464,1273,1148],{"class":474},[464,1275,1276],{"class":1113}," Login",[464,1278,1117],{"class":474},[464,1280,1281],{"class":1120},"ctx",[464,1283,1284],{"class":478}," context",[464,1286,651],{"class":474},[464,1288,1289],{"class":478},"Context",[464,1291,1131],{"class":474},[464,1293,1294],{"class":1120}," email",[464,1296,1131],{"class":474},[464,1298,1299],{"class":1120}," password",[464,1301,1302],{"class":937}," string",[464,1304,1148],{"class":474},[464,1306,1264],{"class":474},[464,1308,1309],{"class":478},"LoginResult",[464,1311,1131],{"class":474},[464,1313,1314],{"class":937}," error",[464,1316,1148],{"class":474},[464,1318,692],{"class":474},[464,1320,1322,1325,1327,1330,1333,1336,1338,1341,1343,1346,1348,1350,1352,1354],{"class":466,"line":1321},19,[464,1323,1324],{"class":698},"    user",[464,1326,1131],{"class":474},[464,1328,1329],{"class":698}," err ",[464,1331,1332],{"class":474},":=",[464,1334,1335],{"class":698}," s",[464,1337,651],{"class":474},[464,1339,1340],{"class":698},"users",[464,1342,651],{"class":474},[464,1344,1345],{"class":1113},"FindByEmail",[464,1347,1117],{"class":474},[464,1349,1281],{"class":698},[464,1351,1131],{"class":474},[464,1353,1294],{"class":698},[464,1355,1356],{"class":474},")\n",[464,1358,1360,1363,1365,1368,1371,1374,1377,1380,1382,1385,1387,1390,1392],{"class":466,"line":1359},20,[464,1361,1362],{"class":470},"    if",[464,1364,1329],{"class":698},[464,1366,1367],{"class":474},"!=",[464,1369,1370],{"class":474}," nil",[464,1372,1373],{"class":474}," ||",[464,1375,1376],{"class":474}," !",[464,1378,1379],{"class":698},"user",[464,1381,651],{"class":474},[464,1383,1384],{"class":1113},"CheckPassword",[464,1386,1117],{"class":474},[464,1388,1389],{"class":698},"password",[464,1391,1148],{"class":474},[464,1393,692],{"class":474},[464,1395,1397,1400,1403,1406],{"class":466,"line":1396},21,[464,1398,1399],{"class":470},"        return",[464,1401,1402],{"class":478}," LoginResult",[464,1404,1405],{"class":474},"{},",[464,1407,1408],{"class":698}," ErrInvalidCredentials\n",[464,1410,1412],{"class":466,"line":1411},22,[464,1413,1246],{"class":474},[464,1415,1417],{"class":466,"line":1416},23,[464,1418,1104],{"emptyLinePlaceholder":1103},[464,1420,1422,1425,1427,1429,1431,1433,1435,1438,1440,1443,1445,1448,1450,1453,1455,1457,1459,1462],{"class":466,"line":1421},24,[464,1423,1424],{"class":698},"    claims ",[464,1426,1332],{"class":474},[464,1428,1124],{"class":698},[464,1430,651],{"class":474},[464,1432,998],{"class":1113},[464,1434,1117],{"class":474},[464,1436,1437],{"class":698},"s",[464,1439,651],{"class":474},[464,1441,1442],{"class":698},"jwt",[464,1444,1131],{"class":474},[464,1446,1447],{"class":698}," user",[464,1449,651],{"class":474},[464,1451,1452],{"class":698},"ID",[464,1454,1131],{"class":474},[464,1456,1447],{"class":698},[464,1458,651],{"class":474},[464,1460,1461],{"class":698},"Name",[464,1463,1184],{"class":474},[464,1465,1467,1470,1472,1475,1477,1479,1481,1484,1486],{"class":466,"line":1466},25,[464,1468,1469],{"class":698},"        user",[464,1471,651],{"class":474},[464,1473,1474],{"class":698},"Roles",[464,1476,1131],{"class":474},[464,1478,1447],{"class":698},[464,1480,651],{"class":474},[464,1482,1483],{"class":698},"Permissions",[464,1485,1148],{"class":474},[464,1487,1488],{"class":617}," \u002F\u002F iat\u002Fexp\u002Fiss\u002Fsub stamped from config\n",[464,1490,1492,1495,1497,1499,1501,1503,1505,1508,1510,1513,1515,1518],{"class":466,"line":1491},26,[464,1493,1494],{"class":698},"    access",[464,1496,1131],{"class":474},[464,1498,1329],{"class":698},[464,1500,1332],{"class":474},[464,1502,1335],{"class":698},[464,1504,651],{"class":474},[464,1506,1507],{"class":698},"issuer",[464,1509,651],{"class":474},[464,1511,1512],{"class":1113},"Sign",[464,1514,1117],{"class":474},[464,1516,1517],{"class":698},"claims",[464,1519,1356],{"class":474},[464,1521,1523,1525,1527,1529,1531],{"class":466,"line":1522},27,[464,1524,1362],{"class":470},[464,1526,1329],{"class":698},[464,1528,1367],{"class":474},[464,1530,1370],{"class":474},[464,1532,692],{"class":474},[464,1534,1536,1538,1540,1542],{"class":466,"line":1535},28,[464,1537,1399],{"class":470},[464,1539,1402],{"class":478},[464,1541,1405],{"class":474},[464,1543,1544],{"class":698}," err\n",[464,1546,1548],{"class":466,"line":1547},29,[464,1549,1246],{"class":474},[464,1551,1553],{"class":466,"line":1552},30,[464,1554,1104],{"emptyLinePlaceholder":1103},[464,1556,1558,1561,1563,1565,1567,1569,1571,1574,1577],{"class":466,"line":1557},31,[464,1559,1560],{"class":698},"    refresh",[464,1562,1131],{"class":474},[464,1564,1329],{"class":698},[464,1566,1332],{"class":474},[464,1568,1124],{"class":698},[464,1570,651],{"class":474},[464,1572,1573],{"class":1113},"NewRefreshToken",[464,1575,1576],{"class":474},"()",[464,1578,1579],{"class":617}," \u002F\u002F 32 bytes crypto\u002Frand, hex\n",[464,1581,1583,1585,1587,1589,1591],{"class":466,"line":1582},32,[464,1584,1362],{"class":470},[464,1586,1329],{"class":698},[464,1588,1367],{"class":474},[464,1590,1370],{"class":474},[464,1592,692],{"class":474},[464,1594,1596,1598,1600,1602],{"class":466,"line":1595},33,[464,1597,1399],{"class":470},[464,1599,1402],{"class":478},[464,1601,1405],{"class":474},[464,1603,1544],{"class":698},[464,1605,1607],{"class":466,"line":1606},34,[464,1608,1246],{"class":474},[464,1610,1612,1615,1617,1620,1622,1625,1628,1631,1633,1635,1637,1639,1641,1643],{"class":466,"line":1611},35,[464,1613,1614],{"class":698},"    expiresAt ",[464,1616,1332],{"class":474},[464,1618,1619],{"class":698}," time",[464,1621,651],{"class":474},[464,1623,1624],{"class":1113},"Now",[464,1626,1627],{"class":474},"().",[464,1629,1630],{"class":1113},"Add",[464,1632,1117],{"class":474},[464,1634,1437],{"class":698},[464,1636,651],{"class":474},[464,1638,1442],{"class":698},[464,1640,651],{"class":474},[464,1642,829],{"class":698},[464,1644,1356],{"class":474},[464,1646,1648,1650,1652,1654,1656,1658,1661,1663,1666,1668,1670,1672,1674,1676,1678,1680,1683,1685,1688,1691,1694,1697,1699,1701,1703],{"class":466,"line":1647},36,[464,1649,1362],{"class":470},[464,1651,1329],{"class":698},[464,1653,1332],{"class":474},[464,1655,1335],{"class":698},[464,1657,651],{"class":474},[464,1659,1660],{"class":698},"tokens",[464,1662,651],{"class":474},[464,1664,1665],{"class":1113},"Store",[464,1667,1117],{"class":474},[464,1669,1281],{"class":698},[464,1671,1131],{"class":474},[464,1673,1447],{"class":698},[464,1675,651],{"class":474},[464,1677,1452],{"class":698},[464,1679,1131],{"class":474},[464,1681,1682],{"class":1113}," sha256Hex",[464,1684,1117],{"class":474},[464,1686,1687],{"class":698},"refresh",[464,1689,1690],{"class":474},"),",[464,1692,1693],{"class":698}," expiresAt",[464,1695,1696],{"class":474},");",[464,1698,1329],{"class":698},[464,1700,1367],{"class":474},[464,1702,1370],{"class":474},[464,1704,692],{"class":474},[464,1706,1708,1710,1712,1714],{"class":466,"line":1707},37,[464,1709,1399],{"class":470},[464,1711,1402],{"class":478},[464,1713,1405],{"class":474},[464,1715,1544],{"class":698},[464,1717,1719],{"class":466,"line":1718},38,[464,1720,1246],{"class":474},[464,1722,1724],{"class":466,"line":1723},39,[464,1725,1104],{"emptyLinePlaceholder":1103},[464,1727,1729,1731,1733,1736,1739,1741,1744,1746,1749,1751,1754,1757],{"class":466,"line":1728},40,[464,1730,1162],{"class":470},[464,1732,1402],{"class":478},[464,1734,1735],{"class":474},"{",[464,1737,1738],{"class":698},"AccessToken",[464,1740,913],{"class":474},[464,1742,1743],{"class":698}," access",[464,1745,1131],{"class":474},[464,1747,1748],{"class":698}," RefreshToken",[464,1750,913],{"class":474},[464,1752,1753],{"class":698}," refresh",[464,1755,1756],{"class":474},"},",[464,1758,1759],{"class":474}," nil\n",[464,1761,1763],{"class":466,"line":1762},41,[464,1764,760],{"class":474},[867,1766,1767,1768,1770,1771,1774,1775,1777,1778,1781],{},"Refresh token handling is your schema: ",[451,1769,1573],{}," only returns a cryptographically random string, the kit does not store it. Server-side, store ",[489,1772,1773],{},"only its hash"," (as with passwords), and use ",[451,1776,829],{}," as the expiry (the ",[451,1779,1780],{},"sha256Hex"," above is your own helper).",[594,1783,1785],{"id":1784},"identify-turning-a-token-into-an-identity","Identify: turning a token into an identity",[447,1787,1788,1790,1791,1794,1795,1797,1798,1800,1801,1804],{},[451,1789,517],{}," does the work a middleware usually hides: it extracts the bearer token from an ",[451,1792,1793],{},"Authorization"," header ",[489,1796,552],{},", validates it with the ",[451,1799,1018],{}," (signature, expiry, issuer\u002Faudience), and maps the resulting claims onto an ",[451,1802,1803],{},"*rbac.Identity",". It takes a header value, not a request or a response writer, and returns a plain error on failure:",[455,1806,1808],{"className":457,"code":1807,"language":459,"meta":460,"style":460},"id, err := auth.Identify(issuer, auth.DefaultIdentity, authorizationHeader)\nswitch {\ncase err == nil:\n    \u002F\u002F id is populated; proceed\ncase errors.Is(err, auth.ErrMissingToken):\n    \u002F\u002F no bearer token at all\ncase errors.Is(err, auth.ErrInvalidToken):\n    \u002F\u002F bad signature, expired, wrong issuer\u002Faudience\ncase errors.Is(err, auth.ErrInvalidClaims):\n    \u002F\u002F token valid, but toIdentity rejected the claims\n}\n",[451,1809,1810,1847,1854,1867,1872,1901,1906,1931,1936,1961,1966],{"__ignoreMap":460},[464,1811,1812,1815,1817,1819,1821,1823,1825,1827,1829,1831,1833,1835,1837,1840,1842,1845],{"class":466,"line":467},[464,1813,1814],{"class":698},"id",[464,1816,1131],{"class":474},[464,1818,1329],{"class":698},[464,1820,1332],{"class":474},[464,1822,1124],{"class":698},[464,1824,651],{"class":474},[464,1826,517],{"class":1113},[464,1828,1117],{"class":474},[464,1830,1507],{"class":698},[464,1832,1131],{"class":474},[464,1834,1124],{"class":698},[464,1836,651],{"class":474},[464,1838,1839],{"class":698},"DefaultIdentity",[464,1841,1131],{"class":474},[464,1843,1844],{"class":698}," authorizationHeader",[464,1846,1356],{"class":474},[464,1848,1849,1852],{"class":466,"line":621},[464,1850,1851],{"class":470},"switch",[464,1853,692],{"class":474},[464,1855,1856,1859,1861,1864],{"class":466,"line":695},[464,1857,1858],{"class":470},"case",[464,1860,1329],{"class":698},[464,1862,1863],{"class":474},"==",[464,1865,1866],{"class":474}," nil:\n",[464,1868,1869],{"class":466,"line":709},[464,1870,1871],{"class":617},"    \u002F\u002F id is populated; proceed\n",[464,1873,1874,1876,1879,1881,1884,1886,1889,1891,1893,1895,1898],{"class":466,"line":731},[464,1875,1858],{"class":470},[464,1877,1878],{"class":698}," errors",[464,1880,651],{"class":474},[464,1882,1883],{"class":1113},"Is",[464,1885,1117],{"class":474},[464,1887,1888],{"class":698},"err",[464,1890,1131],{"class":474},[464,1892,1124],{"class":698},[464,1894,651],{"class":474},[464,1896,1897],{"class":698},"ErrMissingToken",[464,1899,1900],{"class":474},"):\n",[464,1902,1903],{"class":466,"line":751},[464,1904,1905],{"class":617},"    \u002F\u002F no bearer token at all\n",[464,1907,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1929],{"class":466,"line":757},[464,1909,1858],{"class":470},[464,1911,1878],{"class":698},[464,1913,651],{"class":474},[464,1915,1883],{"class":1113},[464,1917,1117],{"class":474},[464,1919,1888],{"class":698},[464,1921,1131],{"class":474},[464,1923,1124],{"class":698},[464,1925,651],{"class":474},[464,1927,1928],{"class":698},"ErrInvalidToken",[464,1930,1900],{"class":474},[464,1932,1933],{"class":466,"line":1100},[464,1934,1935],{"class":617},"    \u002F\u002F bad signature, expired, wrong issuer\u002Faudience\n",[464,1937,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1959],{"class":466,"line":1107},[464,1939,1858],{"class":470},[464,1941,1878],{"class":698},[464,1943,651],{"class":474},[464,1945,1883],{"class":1113},[464,1947,1117],{"class":474},[464,1949,1888],{"class":698},[464,1951,1131],{"class":474},[464,1953,1124],{"class":698},[464,1955,651],{"class":474},[464,1957,1958],{"class":698},"ErrInvalidClaims",[464,1960,1900],{"class":474},[464,1962,1963],{"class":466,"line":1159},[464,1964,1965],{"class":617},"    \u002F\u002F token valid, but toIdentity rejected the claims\n",[464,1967,1968],{"class":466,"line":1173},[464,1969,760],{"class":474},[447,1971,1972,1975,1976,1979,1980,1982,1983,1987],{},[451,1973,1974],{},"auth.DefaultIdentity"," is the ",[451,1977,1978],{},"toIdentity"," function for ",[451,1981,906],{},"; a custom claims type provides its own (see ",[494,1984,1986],{"href":1985},"#custom-claims","Custom claims"," below).",[1989,1990,1992],"h3",{"id":1991},"with-the-kit-serverauthmiddleware","With the kit: server.AuthMiddleware",[447,1994,1995,1996,1998,1999,2001,2002,2005,2006,2009,2010,2013,2014,2016],{},"The gpsystem kit wraps ",[451,1997,517],{}," in a ",[451,2000,556],{}," middleware and renders its errors as RFC 9457 ",[494,2003,2004],{"href":395},"problem+json"," responses. This is how the shop's ",[451,2007,2008],{},"admin"," surface goes behind JWT + role protection: you add the middleware inside the generated ",[451,2011,2012],{},"Register\u003CSurface>"," function, without touching the other surfaces (see ",[494,2015,329],{"href":330},"):",[455,2018,2021],{"className":457,"code":2019,"filename":2020,"language":459,"meta":460,"style":460},"func RegisterAdmin(router chi.Router, deps Dependencies) {\n    adminSvc := adminservice.New()\n    adminHandler := adminhttp.New(adminSvc)\n    adminPolicies := adminpolicy.New()\n    router.Route(\"\u002Fadmin\u002Fshop\", func(r chi.Router) {\n        r.Use(server.AuthMiddleware(deps.Auth), server.RequireRole(\"admin\"))\n        shopadmingen.HandlerWithOptions(\u002F* ... generated strict-handler wiring unchanged ... *\u002F)\n    })\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go",[451,2022,2023,2055,2073,2094,2110,2148,2198,2215,2220],{"__ignoreMap":460},[464,2024,2025,2027,2030,2032,2035,2038,2040,2043,2045,2048,2051,2053],{"class":466,"line":467},[464,2026,1110],{"class":474},[464,2028,2029],{"class":1113}," RegisterAdmin",[464,2031,1117],{"class":474},[464,2033,2034],{"class":1120},"router",[464,2036,2037],{"class":478}," chi",[464,2039,651],{"class":474},[464,2041,2042],{"class":478},"Router",[464,2044,1131],{"class":474},[464,2046,2047],{"class":1120}," deps",[464,2049,2050],{"class":478}," Dependencies",[464,2052,1148],{"class":474},[464,2054,692],{"class":474},[464,2056,2057,2060,2062,2065,2067,2070],{"class":466,"line":621},[464,2058,2059],{"class":698},"    adminSvc ",[464,2061,1332],{"class":474},[464,2063,2064],{"class":698}," adminservice",[464,2066,651],{"class":474},[464,2068,2069],{"class":1113},"New",[464,2071,2072],{"class":474},"()\n",[464,2074,2075,2078,2080,2083,2085,2087,2089,2092],{"class":466,"line":695},[464,2076,2077],{"class":698},"    adminHandler ",[464,2079,1332],{"class":474},[464,2081,2082],{"class":698}," adminhttp",[464,2084,651],{"class":474},[464,2086,2069],{"class":1113},[464,2088,1117],{"class":474},[464,2090,2091],{"class":698},"adminSvc",[464,2093,1356],{"class":474},[464,2095,2096,2099,2101,2104,2106,2108],{"class":466,"line":709},[464,2097,2098],{"class":698},"    adminPolicies ",[464,2100,1332],{"class":474},[464,2102,2103],{"class":698}," adminpolicy",[464,2105,651],{"class":474},[464,2107,2069],{"class":1113},[464,2109,2072],{"class":474},[464,2111,2112,2115,2117,2120,2122,2125,2128,2130,2132,2135,2138,2140,2142,2144,2146],{"class":466,"line":731},[464,2113,2114],{"class":698},"    router",[464,2116,651],{"class":474},[464,2118,2119],{"class":1113},"Route",[464,2121,1117],{"class":474},[464,2123,2124],{"class":474},"\"",[464,2126,2127],{"class":610},"\u002Fadmin\u002Fshop",[464,2129,2124],{"class":474},[464,2131,1131],{"class":474},[464,2133,2134],{"class":474}," func(",[464,2136,2137],{"class":1120},"r",[464,2139,2037],{"class":478},[464,2141,651],{"class":474},[464,2143,2042],{"class":478},[464,2145,1148],{"class":474},[464,2147,692],{"class":474},[464,2149,2150,2153,2155,2158,2160,2162,2164,2167,2169,2172,2174,2177,2179,2182,2184,2187,2189,2191,2193,2195],{"class":466,"line":751},[464,2151,2152],{"class":698},"        r",[464,2154,651],{"class":474},[464,2156,2157],{"class":1113},"Use",[464,2159,1117],{"class":474},[464,2161,569],{"class":698},[464,2163,651],{"class":474},[464,2165,2166],{"class":1113},"AuthMiddleware",[464,2168,1117],{"class":474},[464,2170,2171],{"class":698},"deps",[464,2173,651],{"class":474},[464,2175,2176],{"class":698},"Auth",[464,2178,1690],{"class":474},[464,2180,2181],{"class":698}," server",[464,2183,651],{"class":474},[464,2185,2186],{"class":1113},"RequireRole",[464,2188,1117],{"class":474},[464,2190,2124],{"class":474},[464,2192,2008],{"class":610},[464,2194,2124],{"class":474},[464,2196,2197],{"class":474},"))\n",[464,2199,2200,2203,2205,2208,2210,2213],{"class":466,"line":757},[464,2201,2202],{"class":698},"        shopadmingen",[464,2204,651],{"class":474},[464,2206,2207],{"class":1113},"HandlerWithOptions",[464,2209,1117],{"class":474},[464,2211,2212],{"class":617},"\u002F* ... generated strict-handler wiring unchanged ... *\u002F",[464,2214,1356],{"class":474},[464,2216,2217],{"class":466,"line":1100},[464,2218,2219],{"class":474},"    })\n",[464,2221,2222],{"class":466,"line":1107},[464,2223,760],{"class":474},[447,2225,2226,2227,2229,2230,2233,2234,2237,2238,2241,2242,2245,2246,2249,2250,2253,2254,884,2257,884,2260,2263,2264,2266,2267,2270,2271,2274],{},"You pass ",[451,2228,659],{}," in through the module's ",[451,2231,2232],{},"Dependencies"," struct (an ",[451,2235,2236],{},"Auth auth.Config"," field at the ",[451,2239,2240],{},"\u002F\u002F gpsystem:dependencies"," anchor, ",[451,2243,2244],{},"Auth: cfg.JWT"," in ",[451,2247,2248],{},"main.go","). This way ",[451,2251,2252],{},"register.go"," never depends on the platform config. ",[451,2255,2256],{},"server.AuthMiddleware",[451,2258,2259],{},"server.RequireRole",[451,2261,2262],{},"server.RequirePermission"," live in the kit, not in ",[451,2265,82],{}," itself: see ",[494,2268,2269],{"href":258},"the kit server"," for the full ",[451,2272,2273],{},"server.WriteError"," mapping.",[1989,2276,2278],{"id":2277},"mixed-surfaces-public-and-protected-operations-side-by-side","Mixed surfaces: public and protected operations side by side",[447,2280,2281,2283,2284,2287,2288,2290,2291,2294,2295,2298],{},[451,2282,2256],{}," rejects ",[489,2285,2286],{},"every"," tokenless request with 401; for an all-protected surface (like ",[451,2289,2008],{},") that is exactly what you want. The shop's ",[451,2292,2293],{},"api"," surface is mixed, though: product browsing is anonymous, while the order operations require an identity. In that case, make token parsing conditional, and leave the 401 for tagged operations to the ",[494,2296,2297],{"href":99},"policy enforcer",", which rejects without an identity anyway:",[455,2300,2302],{"className":457,"code":2301,"language":459,"meta":460,"style":460},"authOptional := server.AuthMiddleware(deps.Auth)\nr.Use(func(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        if r.Header.Get(\"Authorization\") == \"\" {\n            next.ServeHTTP(w, r) \u002F\u002F anonymous browsing passes; tagged operations get 401 from the enforcer\n            return\n        }\n        authOptional(next).ServeHTTP(w, r) \u002F\u002F if a token is present, it must be valid\n    })\n})\n",[451,2303,2304,2327,2359,2401,2436,2459,2464,2469,2496,2500],{"__ignoreMap":460},[464,2305,2306,2309,2311,2313,2315,2317,2319,2321,2323,2325],{"class":466,"line":467},[464,2307,2308],{"class":698},"authOptional ",[464,2310,1332],{"class":474},[464,2312,2181],{"class":698},[464,2314,651],{"class":474},[464,2316,2166],{"class":1113},[464,2318,1117],{"class":474},[464,2320,2171],{"class":698},[464,2322,651],{"class":474},[464,2324,2176],{"class":698},[464,2326,1356],{"class":474},[464,2328,2329,2331,2333,2335,2338,2341,2344,2346,2349,2351,2353,2355,2357],{"class":466,"line":621},[464,2330,2137],{"class":698},[464,2332,651],{"class":474},[464,2334,2157],{"class":1113},[464,2336,2337],{"class":474},"(func(",[464,2339,2340],{"class":1120},"next",[464,2342,2343],{"class":478}," http",[464,2345,651],{"class":474},[464,2347,2348],{"class":478},"Handler",[464,2350,1148],{"class":474},[464,2352,2343],{"class":478},[464,2354,651],{"class":474},[464,2356,2348],{"class":478},[464,2358,692],{"class":474},[464,2360,2361,2363,2365,2367,2370,2372,2375,2377,2379,2382,2384,2387,2389,2392,2394,2397,2399],{"class":466,"line":695},[464,2362,1162],{"class":470},[464,2364,2343],{"class":698},[464,2366,651],{"class":474},[464,2368,2369],{"class":1113},"HandlerFunc",[464,2371,2337],{"class":474},[464,2373,2374],{"class":1120},"w",[464,2376,2343],{"class":478},[464,2378,651],{"class":474},[464,2380,2381],{"class":478},"ResponseWriter",[464,2383,1131],{"class":474},[464,2385,2386],{"class":1120}," r",[464,2388,1151],{"class":474},[464,2390,2391],{"class":478},"http",[464,2393,651],{"class":474},[464,2395,2396],{"class":478},"Request",[464,2398,1148],{"class":474},[464,2400,692],{"class":474},[464,2402,2403,2406,2408,2410,2413,2415,2418,2420,2422,2424,2426,2428,2431,2434],{"class":466,"line":709},[464,2404,2405],{"class":470},"        if",[464,2407,2386],{"class":698},[464,2409,651],{"class":474},[464,2411,2412],{"class":698},"Header",[464,2414,651],{"class":474},[464,2416,2417],{"class":1113},"Get",[464,2419,1117],{"class":474},[464,2421,2124],{"class":474},[464,2423,1793],{"class":610},[464,2425,2124],{"class":474},[464,2427,1148],{"class":474},[464,2429,2430],{"class":474}," ==",[464,2432,2433],{"class":474}," \"\"",[464,2435,692],{"class":474},[464,2437,2438,2441,2443,2446,2448,2450,2452,2454,2456],{"class":466,"line":731},[464,2439,2440],{"class":698},"            next",[464,2442,651],{"class":474},[464,2444,2445],{"class":1113},"ServeHTTP",[464,2447,1117],{"class":474},[464,2449,2374],{"class":698},[464,2451,1131],{"class":474},[464,2453,2386],{"class":698},[464,2455,1148],{"class":474},[464,2457,2458],{"class":617}," \u002F\u002F anonymous browsing passes; tagged operations get 401 from the enforcer\n",[464,2460,2461],{"class":466,"line":751},[464,2462,2463],{"class":470},"            return\n",[464,2465,2466],{"class":466,"line":757},[464,2467,2468],{"class":474},"        }\n",[464,2470,2471,2474,2476,2478,2481,2483,2485,2487,2489,2491,2493],{"class":466,"line":1100},[464,2472,2473],{"class":1113},"        authOptional",[464,2475,1117],{"class":474},[464,2477,2340],{"class":698},[464,2479,2480],{"class":474},").",[464,2482,2445],{"class":1113},[464,2484,1117],{"class":474},[464,2486,2374],{"class":698},[464,2488,1131],{"class":474},[464,2490,2386],{"class":698},[464,2492,1148],{"class":474},[464,2494,2495],{"class":617}," \u002F\u002F if a token is present, it must be valid\n",[464,2497,2498],{"class":466,"line":1107},[464,2499,2219],{"class":474},[464,2501,2502],{"class":466,"line":1159},[464,2503,2504],{"class":474},"})\n",[594,2506,1986],{"id":2507},"custom-claims",[447,2509,2510,2511,2513,2514,1979,2516,2518,2519,2522,2523,2525],{},"When ",[451,2512,906],{}," is not enough (say you carry a tenant identifier in the token), define your own claims type and write your own ",[451,2515,1978],{},[451,2517,517],{}," (or ",[451,2520,2521],{},"server.AuthMiddlewareFor",", its kit-wired ",[451,2524,556],{}," counterpart):",[455,2527,2529],{"className":457,"code":2528,"language":459,"meta":460,"style":460},"type MyClaims struct {\n    TenantID string `json:\"tenant\"`\n    jwt.RegisteredClaims\n}\n\ntoIdentity := func(c MyClaims) (*rbac.Identity, error) {\n    return &rbac.Identity{\n        Subject: c.Subject,\n        Extra:   map[string]any{\"tenant\": c.TenantID},\n    }, nil\n}\n\nissuer := auth.NewTokenIssuer[MyClaims](cfg.JWT)\nmw := server.AuthMiddlewareFor(issuer, toIdentity) \u002F\u002F kit net\u002Fhttp middleware\nid, err := auth.Identify(issuer, toIdentity, authorizationHeader) \u002F\u002F or call it directly\n",[451,2530,2531,2542,2556,2564,2568,2572,2606,2620,2637,2676,2683,2687,2691,2721,2749],{"__ignoreMap":460},[464,2532,2533,2535,2538,2540],{"class":466,"line":467},[464,2534,683],{"class":474},[464,2536,2537],{"class":478}," MyClaims",[464,2539,689],{"class":474},[464,2541,692],{"class":474},[464,2543,2544,2547,2549,2551,2554],{"class":466,"line":621},[464,2545,2546],{"class":698},"    TenantID ",[464,2548,938],{"class":937},[464,2550,743],{"class":474},[464,2552,2553],{"class":610},"json:\"tenant\"",[464,2555,728],{"class":474},[464,2557,2558,2560,2562],{"class":466,"line":695},[464,2559,984],{"class":478},[464,2561,651],{"class":474},[464,2563,989],{"class":478},[464,2565,2566],{"class":466,"line":709},[464,2567,760],{"class":474},[464,2569,2570],{"class":466,"line":731},[464,2571,1104],{"emptyLinePlaceholder":1103},[464,2573,2574,2577,2579,2581,2584,2586,2588,2591,2594,2596,2598,2600,2602,2604],{"class":466,"line":751},[464,2575,2576],{"class":698},"toIdentity ",[464,2578,1332],{"class":474},[464,2580,2134],{"class":474},[464,2582,2583],{"class":1120},"c",[464,2585,2537],{"class":478},[464,2587,1148],{"class":474},[464,2589,2590],{"class":474}," (*",[464,2592,2593],{"class":478},"rbac",[464,2595,651],{"class":474},[464,2597,535],{"class":478},[464,2599,1131],{"class":474},[464,2601,1314],{"class":937},[464,2603,1148],{"class":474},[464,2605,692],{"class":474},[464,2607,2608,2610,2612,2614,2616,2618],{"class":466,"line":757},[464,2609,1162],{"class":470},[464,2611,1165],{"class":474},[464,2613,2593],{"class":478},[464,2615,651],{"class":474},[464,2617,535],{"class":478},[464,2619,1170],{"class":474},[464,2621,2622,2625,2627,2630,2632,2635],{"class":466,"line":1100},[464,2623,2624],{"class":698},"        Subject",[464,2626,913],{"class":474},[464,2628,2629],{"class":698}," c",[464,2631,651],{"class":474},[464,2633,2634],{"class":698},"Subject",[464,2636,1184],{"class":474},[464,2638,2639,2642,2644,2647,2649,2652,2655,2657,2659,2662,2664,2666,2668,2670,2673],{"class":466,"line":1107},[464,2640,2641],{"class":698},"        Extra",[464,2643,913],{"class":474},[464,2645,2646],{"class":474},"   map[",[464,2648,938],{"class":937},[464,2650,2651],{"class":474},"]",[464,2653,2654],{"class":478},"any",[464,2656,1735],{"class":474},[464,2658,2124],{"class":474},[464,2660,2661],{"class":610},"tenant",[464,2663,2124],{"class":474},[464,2665,913],{"class":474},[464,2667,2629],{"class":698},[464,2669,651],{"class":474},[464,2671,2672],{"class":698},"TenantID",[464,2674,2675],{"class":474},"},\n",[464,2677,2678,2681],{"class":466,"line":1159},[464,2679,2680],{"class":474},"    },",[464,2682,1759],{"class":474},[464,2684,2685],{"class":466,"line":1173},[464,2686,760],{"class":474},[464,2688,2689],{"class":466,"line":1187},[464,2690,1104],{"emptyLinePlaceholder":1103},[464,2692,2693,2696,2698,2700,2702,2704,2706,2709,2711,2714,2716,2719],{"class":466,"line":1218},[464,2694,2695],{"class":698},"issuer ",[464,2697,1332],{"class":474},[464,2699,1124],{"class":698},[464,2701,651],{"class":474},[464,2703,1199],{"class":1113},[464,2705,1068],{"class":474},[464,2707,2708],{"class":478},"MyClaims",[464,2710,1210],{"class":474},[464,2712,2713],{"class":698},"cfg",[464,2715,651],{"class":474},[464,2717,2718],{"class":698},"JWT",[464,2720,1356],{"class":474},[464,2722,2723,2726,2728,2730,2732,2735,2737,2739,2741,2744,2746],{"class":466,"line":1231},[464,2724,2725],{"class":698},"mw ",[464,2727,1332],{"class":474},[464,2729,2181],{"class":698},[464,2731,651],{"class":474},[464,2733,2734],{"class":1113},"AuthMiddlewareFor",[464,2736,1117],{"class":474},[464,2738,1507],{"class":698},[464,2740,1131],{"class":474},[464,2742,2743],{"class":698}," toIdentity",[464,2745,1148],{"class":474},[464,2747,2748],{"class":617}," \u002F\u002F kit net\u002Fhttp middleware\n",[464,2750,2751,2753,2755,2757,2759,2761,2763,2765,2767,2769,2771,2773,2775,2777,2779],{"class":466,"line":1243},[464,2752,1814],{"class":698},[464,2754,1131],{"class":474},[464,2756,1329],{"class":698},[464,2758,1332],{"class":474},[464,2760,1124],{"class":698},[464,2762,651],{"class":474},[464,2764,517],{"class":1113},[464,2766,1117],{"class":474},[464,2768,1507],{"class":698},[464,2770,1131],{"class":474},[464,2772,2743],{"class":698},[464,2774,1131],{"class":474},[464,2776,1844],{"class":698},[464,2778,1148],{"class":474},[464,2780,2781],{"class":617}," \u002F\u002F or call it directly\n",[447,2783,2784,2786,2787,2789,2790,637,2792,2794,2795,2798,2799,2801,2802,2804,2805,2807],{},[451,2785,2256],{}," is exactly ",[451,2788,2521],{}," pre-wired with ",[451,2791,906],{},[451,2793,1974],{},", so both share the same ",[451,2796,2797],{},"func(http.Handler) http.Handler"," shape and the same ",[451,2800,535],{},"-construction contract; reach for ",[451,2803,2734],{}," only when ",[451,2806,906],{}," doesn't carry what you need.",[594,2809,2811],{"id":2810},"hs256-and-secret-management","HS256 and secret management",[447,2813,2814,2815,2818,2819,2822,2823,2826,2827,2829],{},"Signing is ",[489,2816,2817],{},"HS256"," (shared secret). Parsing pins the algorithm (",[451,2820,2821],{},"WithValidMethods","), requires an expiry (",[451,2824,2825],{},"WithExpirationRequired","), and when configured, checks issuer and audience, so alg-confusion attacks and unsigned tokens are rejected. The ",[451,2828,1018],{}," API is claims-generic, so asymmetric algorithms can be added later without breaking consumers.",[447,2831,2832,2833,2836,2837,2840,2841,2843],{},"The secret should be long and random (e.g. ",[451,2834,2835],{},"openssl rand -hex 32",") and come from a secret manager: never commit it to the repo. ",[451,2838,2839],{},".env"," is for local development only; see the ",[494,2842,654],{"href":229}," page.",[594,2845,2847],{"id":2846},"standalone-no-kit","Standalone, no kit",[447,2849,2850,2851,2854,2855,2857,2858,884,2860,2863,2864,2866,2867,2869],{},"Nothing above needs ",[451,2852,2853],{},"server.Run"," or any other kit package. ",[451,2856,517],{}," and the ",[451,2859,2593],{},[451,2861,2862],{},"policy"," sentinel errors are all you need to wire a bare ",[451,2865,556],{}," service, or a non-HTTP one, with your own error rendering: see ",[494,2868,103],{"href":104}," for full, runnable examples.",[447,2871,2872,2873,2875,2876,2878,2879,2881,2882,2885,2886,2889],{},"The next layer: ",[494,2874,527],{"href":94}," over the ",[451,2877,535],{}," the token became, and per-request ",[494,2880,531],{"href":99},". For 3rd-party (Discord\u002FFacebook\u002FApple\u002FGoogle) login, the module ",[451,2883,2884],{},"add auth"," generates has its own ",[494,2887,2888],{"href":277},"social login layer"," calling into this same token issuing.",[594,2891,2893],{"id":2892},"patterns-used","Patterns used",[447,2895,2896,1264,2899,1003,2902,2905,2906,651],{},[489,2897,2898],{},"Generics over the claims type",[451,2900,2901],{},"TokenIssuer[C jwt.Claims]",[451,2903,2904],{},"Identify[C]","): ",[494,2907,238],{"href":2908},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#generics-over-the-claims-type",[2910,2911,2912],"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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":460,"searchDepth":621,"depth":621,"links":2914},[2915,2916,2917,2918,2922,2923,2924,2925],{"id":596,"depth":621,"text":25},{"id":654,"depth":621,"text":204},{"id":899,"depth":621,"text":900},{"id":1784,"depth":621,"text":1785,"children":2919},[2920,2921],{"id":1991,"depth":695,"text":1992},{"id":2277,"depth":695,"text":2278},{"id":2507,"depth":621,"text":1986},{"id":2810,"depth":621,"text":2811},{"id":2846,"depth":621,"text":2847},{"id":2892,"depth":621,"text":2893},"JWT issuing and a transport-agnostic Identify function: stateless auth with no framework baked in.","md",null,{},{"icon":91},{"title":88,"description":2926},"gps4Vmc1ICgeeeDvc4GWqvim6GdLoRwIIZl6V8Uw0-c",[2934,2936],{"title":77,"path":78,"stem":79,"description":2935,"icon":80,"children":-1},"Task scheduling in code, cron events and jobs, replica-safe with a Valkey-lease leader election.",{"title":93,"path":94,"stem":95,"description":2937,"icon":96,"children":-1},"Role and permission checks over the Identity carried in the context: with route guards and service-layer queries.",1785445889222]