[{"data":1,"prerenderedAt":2733},["ShallowReactive",2],{"navigation_docs_en":3,"-en-security-authentication":342,"-en-security-authentication-surround":2728},[4,45,61,96,132,166,196,217,241,265],{"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},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":203,"body":344,"description":2721,"extension":2722,"links":2723,"meta":2724,"navigation":2725,"path":204,"seo":2726,"stem":205,"__hash__":2727},"docs_en\u002Fen\u002F6.security\u002F1.authentication.md",{"type":345,"value":346,"toc":2711},"minimark",[347,377,417,424,443,447,461,559,664,696,700,713,793,819,1564,1581,1585,1612,1626,2011,2041,2046,2064,2396,2400,2417,2618,2638,2642,2660,2674,2685,2689,2707],[348,349,354],"pre",{"className":350,"code":351,"language":352,"meta":353,"style":353},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fgpsystem\u002Fauth\"\n","go","",[355,356,357],"code",{"__ignoreMap":353},[358,359,362,366,370,374],"span",{"class":360,"line":361},"line",1,[358,363,365],{"class":364},"s7zQu","import",[358,367,369],{"class":368},"sMK4o"," \"",[358,371,373],{"class":372},"sBMFI","github.com\u002Fgp-system\u002Fgpsystem\u002Fauth",[358,375,376],{"class":368},"\"\n",[378,379,380,381,384,385,389,390,393,394,397,398,404,405,408,409,412,413,416],"p",{},"The ",[355,382,383],{},"auth"," package provides two things: a ",[386,387,388],"strong",{},"token issuer"," (JWT signing and verification) and a ",[386,391,392],{},"middleware"," that turns the incoming ",[355,395,396],{},"Authorization: Bearer \u003Ctoken>"," header into an ",[399,400,401],"a",{"href":209},[355,402,403],{},"rbac.Identity"," in the request context. From there on, every authorization decision (the ",[399,406,407],{"href":209},"role and permission checks",", the ",[399,410,411],{"href":214},"policies",") happens over the ",[355,414,415],{},"Identity",", independent of the token.",[378,418,419,420,423],{},"Authentication here is built around a ",[386,421,422],{},"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.",[425,426,427,428,434,435,438,439,442],"note",{},"The package builds on ",[399,429,433],{"href":430,"rel":431},"https:\u002F\u002Fgithub.com\u002Fgolang-jwt\u002Fjwt",[432],"nofollow","golang-jwt\u002Fjwt\u002Fv5"," and does not hide it: claims types implement the ",[355,436,437],{},"jwt.Claims"," interface, and a custom claims struct embeds ",[355,440,441],{},"jwt.RegisteredClaims",". The kit centralizes here rather than abstracting: your JWT-ecosystem knowledge transfers one to one.",[444,445,52],"h2",{"id":446},"configuration",[378,448,449,452,453,456,457,460],{},[355,450,451],{},"auth.Config"," loads from the environment; in a generated project's config you add it under the ",[355,454,455],{},"\u002F\u002F gpsystem:config"," anchor with the ",[355,458,459],{},"JWT_"," prefix:",[348,462,464],{"className":350,"code":463,"language":352,"meta":353,"style":353},"\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",[355,465,466,472,487,503,527,547,553],{"__ignoreMap":353},[358,467,468],{"class":360,"line":361},[358,469,471],{"class":470},"sHwdD","\u002F\u002F internal\u002Fplatform\u002Fconfig\u002Fconfig.go\n",[358,473,475,478,481,484],{"class":360,"line":474},2,[358,476,477],{"class":368},"type",[358,479,480],{"class":372}," Config",[358,482,483],{"class":368}," struct",[358,485,486],{"class":368}," {\n",[358,488,490,494,497,500],{"class":360,"line":489},3,[358,491,493],{"class":492},"sTEyZ","    Server ",[358,495,496],{"class":372},"server",[358,498,499],{"class":368},".",[358,501,502],{"class":372},"Config\n",[358,504,506,509,512,514,517,520,524],{"class":360,"line":505},4,[358,507,508],{"class":492},"    DB     ",[358,510,511],{"class":372},"dbx",[358,513,499],{"class":368},[358,515,516],{"class":372},"Config",[358,518,519],{"class":368},"  `",[358,521,523],{"class":522},"sfazB","envPrefix:\"DB_\"",[358,525,526],{"class":368},"`\n",[358,528,530,533,535,537,539,542,545],{"class":360,"line":529},5,[358,531,532],{"class":492},"    JWT    ",[358,534,383],{"class":372},[358,536,499],{"class":368},[358,538,516],{"class":372},[358,540,541],{"class":368}," `",[358,543,544],{"class":522},"envPrefix:\"JWT_\"",[358,546,526],{"class":368},[358,548,550],{"class":360,"line":549},6,[358,551,552],{"class":470},"    \u002F\u002F gpsystem:config\n",[358,554,556],{"class":360,"line":555},7,[358,557,558],{"class":368},"}\n",[560,561,562,578],"table",{},[563,564,565],"thead",{},[566,567,568,572,575],"tr",{},[569,570,571],"th",{},"Env variable",[569,573,574],{},"Field",[569,576,577],{},"Default",[579,580,581,599,616,634,649],"tbody",{},[566,582,583,589,594],{},[584,585,586],"td",{},[355,587,588],{},"JWT_SECRET",[584,590,591],{},[355,592,593],{},"Secret",[584,595,596],{},[386,597,598],{},"required",[566,600,601,606,611],{},[584,602,603],{},[355,604,605],{},"JWT_ACCESS_TOKEN_TTL",[584,607,608],{},[355,609,610],{},"AccessTTL",[584,612,613],{},[355,614,615],{},"15m",[566,617,618,623,628],{},[584,619,620],{},[355,621,622],{},"JWT_REFRESH_TOKEN_TTL",[584,624,625],{},[355,626,627],{},"RefreshTTL",[584,629,630,633],{},[355,631,632],{},"168h"," (7 days)",[566,635,636,641,646],{},[584,637,638],{},[355,639,640],{},"JWT_ISSUER",[584,642,643],{},[355,644,645],{},"Issuer",[584,647,648],{},"empty (issuer check disabled)",[566,650,651,656,661],{},[584,652,653],{},[355,654,655],{},"JWT_AUDIENCE",[584,657,658],{},[355,659,660],{},"Audience",[584,662,663],{},"empty (audience check disabled)",[665,666,667,668,670,671,673,674,680,681,683,684,686,687,683,690,693,694,499],"warning",{},"Set ",[355,669,645],{}," and ",[355,672,660],{},", and use a ",[386,675,676,677,679],{},"distinct ",[355,678,593],{}," per service and per environment",": that way a token minted for another service or for staging cannot be replayed against the production API. When ",[355,682,645],{},"\u002F",[355,685,660],{}," are set, parsing requires a matching ",[355,688,689],{},"iss",[355,691,692],{},"aud"," claim. Full env reference: ",[399,695,52],{"href":53},[444,697,699],{"id":698},"issuing-tokens-the-login-flow","Issuing tokens: the login flow",[378,701,702,703,706,707,709,710,712],{},"The kit's standard claims shape is ",[355,704,705],{},"DefaultClaims",": compatible with the ",[355,708,403],{}," fields, embedding ",[355,711,441],{},":",[348,714,716],{"className":350,"code":715,"language":352,"meta":353,"style":353},"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",[355,717,718,729,746,763,779,789],{"__ignoreMap":353},[358,719,720,722,725,727],{"class":360,"line":361},[358,721,477],{"class":368},[358,723,724],{"class":372}," DefaultClaims",[358,726,483],{"class":368},[358,728,486],{"class":368},[358,730,731,734,738,741,744],{"class":360,"line":474},[358,732,733],{"class":492},"    Username    ",[358,735,737],{"class":736},"spNyl","string",[358,739,740],{"class":368},"   `",[358,742,743],{"class":522},"json:\"username,omitempty\"",[358,745,526],{"class":368},[358,747,748,751,754,756,758,761],{"class":360,"line":489},[358,749,750],{"class":492},"    Roles       ",[358,752,753],{"class":368},"[]",[358,755,737],{"class":736},[358,757,541],{"class":368},[358,759,760],{"class":522},"json:\"roles,omitempty\"",[358,762,526],{"class":368},[358,764,765,768,770,772,774,777],{"class":360,"line":505},[358,766,767],{"class":492},"    Permissions ",[358,769,753],{"class":368},[358,771,737],{"class":736},[358,773,541],{"class":368},[358,775,776],{"class":522},"json:\"permissions,omitempty\"",[358,778,526],{"class":368},[358,780,781,784,786],{"class":360,"line":529},[358,782,783],{"class":372},"    jwt",[358,785,499],{"class":368},[358,787,788],{"class":372},"RegisteredClaims\n",[358,790,791],{"class":360,"line":549},[358,792,558],{"class":368},[378,794,795,798,799,802,803,802,806,802,808,811,812,814,815,818],{},[355,796,797],{},"NewDefaultClaims"," stamps the registered claims (",[355,800,801],{},"iat",", ",[355,804,805],{},"exp",[355,807,689],{},[355,809,810],{},"sub",", plus ",[355,813,692],{}," when configured) from the config; the ",[355,816,817],{},"TokenIssuer"," signs and parses the token. This is what the shop's login service looks like, sketched:",[348,820,822],{"className":350,"code":821,"language":352,"meta":353,"style":353},"\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",[355,823,824,829,840,851,877,885,893,897,904,956,970,984,1015,1028,1040,1046,1051,1056,1118,1156,1193,1208,1213,1218,1263,1288,1319,1332,1344,1349,1354,1379,1392,1403,1408,1444,1504,1515,1520,1525,1559],{"__ignoreMap":353},[358,825,826],{"class":360,"line":361},[358,827,828],{"class":470},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fadmin\u002Fservice\u002Fauth.go (sketch)\n",[358,830,831,833,836,838],{"class":360,"line":474},[358,832,477],{"class":368},[358,834,835],{"class":372}," Service",[358,837,483],{"class":368},[358,839,486],{"class":368},[358,841,842,845,847,849],{"class":360,"line":489},[358,843,844],{"class":492},"    jwt    ",[358,846,383],{"class":372},[358,848,499],{"class":368},[358,850,502],{"class":372},[358,852,853,856,859,861,863,865,868,870,872,874],{"class":360,"line":505},[358,854,855],{"class":492},"    issuer ",[358,857,858],{"class":368},"*",[358,860,383],{"class":372},[358,862,499],{"class":368},[358,864,817],{"class":372},[358,866,867],{"class":368},"[",[358,869,383],{"class":372},[358,871,499],{"class":368},[358,873,705],{"class":372},[358,875,876],{"class":368},"]\n",[358,878,879,882],{"class":360,"line":529},[358,880,881],{"class":492},"    users  ",[358,883,884],{"class":372},"UserRepo\n",[358,886,887,890],{"class":360,"line":549},[358,888,889],{"class":492},"    tokens ",[358,891,892],{"class":372},"RefreshTokenRepo\n",[358,894,895],{"class":360,"line":555},[358,896,558],{"class":368},[358,898,900],{"class":360,"line":899},8,[358,901,903],{"emptyLinePlaceholder":902},true,"\n",[358,905,907,910,914,917,921,924,926,928,931,934,937,939,942,945,948,951,954],{"class":360,"line":906},9,[358,908,909],{"class":368},"func",[358,911,913],{"class":912},"s2Zo4"," New",[358,915,916],{"class":368},"(",[358,918,920],{"class":919},"sHdIc","jwtCfg",[358,922,923],{"class":372}," auth",[358,925,499],{"class":368},[358,927,516],{"class":372},[358,929,930],{"class":368},",",[358,932,933],{"class":919}," users",[358,935,936],{"class":372}," UserRepo",[358,938,930],{"class":368},[358,940,941],{"class":919}," tokens",[358,943,944],{"class":372}," RefreshTokenRepo",[358,946,947],{"class":368},")",[358,949,950],{"class":368}," *",[358,952,953],{"class":372},"Service",[358,955,486],{"class":368},[358,957,959,962,965,967],{"class":360,"line":958},10,[358,960,961],{"class":364},"    return",[358,963,964],{"class":368}," &",[358,966,953],{"class":372},[358,968,969],{"class":368},"{\n",[358,971,973,976,978,981],{"class":360,"line":972},11,[358,974,975],{"class":492},"        jwt",[358,977,712],{"class":368},[358,979,980],{"class":492},"    jwtCfg",[358,982,983],{"class":368},",\n",[358,985,987,990,992,994,996,999,1001,1003,1005,1007,1010,1012],{"class":360,"line":986},12,[358,988,989],{"class":492},"        issuer",[358,991,712],{"class":368},[358,993,923],{"class":492},[358,995,499],{"class":368},[358,997,998],{"class":912},"NewTokenIssuer",[358,1000,867],{"class":368},[358,1002,383],{"class":372},[358,1004,499],{"class":368},[358,1006,705],{"class":372},[358,1008,1009],{"class":368},"](",[358,1011,920],{"class":492},[358,1013,1014],{"class":368},"),\n",[358,1016,1018,1021,1023,1026],{"class":360,"line":1017},13,[358,1019,1020],{"class":492},"        users",[358,1022,712],{"class":368},[358,1024,1025],{"class":492},"  users",[358,1027,983],{"class":368},[358,1029,1031,1034,1036,1038],{"class":360,"line":1030},14,[358,1032,1033],{"class":492},"        tokens",[358,1035,712],{"class":368},[358,1037,941],{"class":492},[358,1039,983],{"class":368},[358,1041,1043],{"class":360,"line":1042},15,[358,1044,1045],{"class":368},"    }\n",[358,1047,1049],{"class":360,"line":1048},16,[358,1050,558],{"class":368},[358,1052,1054],{"class":360,"line":1053},17,[358,1055,903],{"emptyLinePlaceholder":902},[358,1057,1059,1061,1064,1067,1069,1071,1073,1076,1078,1081,1084,1086,1089,1091,1094,1096,1099,1102,1104,1106,1109,1111,1114,1116],{"class":360,"line":1058},18,[358,1060,909],{"class":368},[358,1062,1063],{"class":368}," (",[358,1065,1066],{"class":919},"s ",[358,1068,858],{"class":368},[358,1070,953],{"class":372},[358,1072,947],{"class":368},[358,1074,1075],{"class":912}," Login",[358,1077,916],{"class":368},[358,1079,1080],{"class":919},"ctx",[358,1082,1083],{"class":372}," context",[358,1085,499],{"class":368},[358,1087,1088],{"class":372},"Context",[358,1090,930],{"class":368},[358,1092,1093],{"class":919}," email",[358,1095,930],{"class":368},[358,1097,1098],{"class":919}," password",[358,1100,1101],{"class":736}," string",[358,1103,947],{"class":368},[358,1105,1063],{"class":368},[358,1107,1108],{"class":372},"LoginResult",[358,1110,930],{"class":368},[358,1112,1113],{"class":736}," error",[358,1115,947],{"class":368},[358,1117,486],{"class":368},[358,1119,1121,1124,1126,1129,1132,1135,1137,1140,1142,1145,1147,1149,1151,1153],{"class":360,"line":1120},19,[358,1122,1123],{"class":492},"    user",[358,1125,930],{"class":368},[358,1127,1128],{"class":492}," err ",[358,1130,1131],{"class":368},":=",[358,1133,1134],{"class":492}," s",[358,1136,499],{"class":368},[358,1138,1139],{"class":492},"users",[358,1141,499],{"class":368},[358,1143,1144],{"class":912},"FindByEmail",[358,1146,916],{"class":368},[358,1148,1080],{"class":492},[358,1150,930],{"class":368},[358,1152,1093],{"class":492},[358,1154,1155],{"class":368},")\n",[358,1157,1159,1162,1164,1167,1170,1173,1176,1179,1181,1184,1186,1189,1191],{"class":360,"line":1158},20,[358,1160,1161],{"class":364},"    if",[358,1163,1128],{"class":492},[358,1165,1166],{"class":368},"!=",[358,1168,1169],{"class":368}," nil",[358,1171,1172],{"class":368}," ||",[358,1174,1175],{"class":368}," !",[358,1177,1178],{"class":492},"user",[358,1180,499],{"class":368},[358,1182,1183],{"class":912},"CheckPassword",[358,1185,916],{"class":368},[358,1187,1188],{"class":492},"password",[358,1190,947],{"class":368},[358,1192,486],{"class":368},[358,1194,1196,1199,1202,1205],{"class":360,"line":1195},21,[358,1197,1198],{"class":364},"        return",[358,1200,1201],{"class":372}," LoginResult",[358,1203,1204],{"class":368},"{},",[358,1206,1207],{"class":492}," ErrInvalidCredentials\n",[358,1209,1211],{"class":360,"line":1210},22,[358,1212,1045],{"class":368},[358,1214,1216],{"class":360,"line":1215},23,[358,1217,903],{"emptyLinePlaceholder":902},[358,1219,1221,1224,1226,1228,1230,1232,1234,1237,1239,1242,1244,1247,1249,1252,1254,1256,1258,1261],{"class":360,"line":1220},24,[358,1222,1223],{"class":492},"    claims ",[358,1225,1131],{"class":368},[358,1227,923],{"class":492},[358,1229,499],{"class":368},[358,1231,797],{"class":912},[358,1233,916],{"class":368},[358,1235,1236],{"class":492},"s",[358,1238,499],{"class":368},[358,1240,1241],{"class":492},"jwt",[358,1243,930],{"class":368},[358,1245,1246],{"class":492}," user",[358,1248,499],{"class":368},[358,1250,1251],{"class":492},"ID",[358,1253,930],{"class":368},[358,1255,1246],{"class":492},[358,1257,499],{"class":368},[358,1259,1260],{"class":492},"Name",[358,1262,983],{"class":368},[358,1264,1266,1269,1271,1274,1276,1278,1280,1283,1285],{"class":360,"line":1265},25,[358,1267,1268],{"class":492},"        user",[358,1270,499],{"class":368},[358,1272,1273],{"class":492},"Roles",[358,1275,930],{"class":368},[358,1277,1246],{"class":492},[358,1279,499],{"class":368},[358,1281,1282],{"class":492},"Permissions",[358,1284,947],{"class":368},[358,1286,1287],{"class":470}," \u002F\u002F iat\u002Fexp\u002Fiss\u002Fsub stamped from config\n",[358,1289,1291,1294,1296,1298,1300,1302,1304,1307,1309,1312,1314,1317],{"class":360,"line":1290},26,[358,1292,1293],{"class":492},"    access",[358,1295,930],{"class":368},[358,1297,1128],{"class":492},[358,1299,1131],{"class":368},[358,1301,1134],{"class":492},[358,1303,499],{"class":368},[358,1305,1306],{"class":492},"issuer",[358,1308,499],{"class":368},[358,1310,1311],{"class":912},"Sign",[358,1313,916],{"class":368},[358,1315,1316],{"class":492},"claims",[358,1318,1155],{"class":368},[358,1320,1322,1324,1326,1328,1330],{"class":360,"line":1321},27,[358,1323,1161],{"class":364},[358,1325,1128],{"class":492},[358,1327,1166],{"class":368},[358,1329,1169],{"class":368},[358,1331,486],{"class":368},[358,1333,1335,1337,1339,1341],{"class":360,"line":1334},28,[358,1336,1198],{"class":364},[358,1338,1201],{"class":372},[358,1340,1204],{"class":368},[358,1342,1343],{"class":492}," err\n",[358,1345,1347],{"class":360,"line":1346},29,[358,1348,1045],{"class":368},[358,1350,1352],{"class":360,"line":1351},30,[358,1353,903],{"emptyLinePlaceholder":902},[358,1355,1357,1360,1362,1364,1366,1368,1370,1373,1376],{"class":360,"line":1356},31,[358,1358,1359],{"class":492},"    refresh",[358,1361,930],{"class":368},[358,1363,1128],{"class":492},[358,1365,1131],{"class":368},[358,1367,923],{"class":492},[358,1369,499],{"class":368},[358,1371,1372],{"class":912},"NewRefreshToken",[358,1374,1375],{"class":368},"()",[358,1377,1378],{"class":470}," \u002F\u002F 32 bytes crypto\u002Frand, hex\n",[358,1380,1382,1384,1386,1388,1390],{"class":360,"line":1381},32,[358,1383,1161],{"class":364},[358,1385,1128],{"class":492},[358,1387,1166],{"class":368},[358,1389,1169],{"class":368},[358,1391,486],{"class":368},[358,1393,1395,1397,1399,1401],{"class":360,"line":1394},33,[358,1396,1198],{"class":364},[358,1398,1201],{"class":372},[358,1400,1204],{"class":368},[358,1402,1343],{"class":492},[358,1404,1406],{"class":360,"line":1405},34,[358,1407,1045],{"class":368},[358,1409,1411,1414,1416,1419,1421,1424,1427,1430,1432,1434,1436,1438,1440,1442],{"class":360,"line":1410},35,[358,1412,1413],{"class":492},"    expiresAt ",[358,1415,1131],{"class":368},[358,1417,1418],{"class":492}," time",[358,1420,499],{"class":368},[358,1422,1423],{"class":912},"Now",[358,1425,1426],{"class":368},"().",[358,1428,1429],{"class":912},"Add",[358,1431,916],{"class":368},[358,1433,1236],{"class":492},[358,1435,499],{"class":368},[358,1437,1241],{"class":492},[358,1439,499],{"class":368},[358,1441,627],{"class":492},[358,1443,1155],{"class":368},[358,1445,1447,1449,1451,1453,1455,1457,1460,1462,1465,1467,1469,1471,1473,1475,1477,1479,1482,1484,1487,1490,1493,1496,1498,1500,1502],{"class":360,"line":1446},36,[358,1448,1161],{"class":364},[358,1450,1128],{"class":492},[358,1452,1131],{"class":368},[358,1454,1134],{"class":492},[358,1456,499],{"class":368},[358,1458,1459],{"class":492},"tokens",[358,1461,499],{"class":368},[358,1463,1464],{"class":912},"Store",[358,1466,916],{"class":368},[358,1468,1080],{"class":492},[358,1470,930],{"class":368},[358,1472,1246],{"class":492},[358,1474,499],{"class":368},[358,1476,1251],{"class":492},[358,1478,930],{"class":368},[358,1480,1481],{"class":912}," sha256Hex",[358,1483,916],{"class":368},[358,1485,1486],{"class":492},"refresh",[358,1488,1489],{"class":368},"),",[358,1491,1492],{"class":492}," expiresAt",[358,1494,1495],{"class":368},");",[358,1497,1128],{"class":492},[358,1499,1166],{"class":368},[358,1501,1169],{"class":368},[358,1503,486],{"class":368},[358,1505,1507,1509,1511,1513],{"class":360,"line":1506},37,[358,1508,1198],{"class":364},[358,1510,1201],{"class":372},[358,1512,1204],{"class":368},[358,1514,1343],{"class":492},[358,1516,1518],{"class":360,"line":1517},38,[358,1519,1045],{"class":368},[358,1521,1523],{"class":360,"line":1522},39,[358,1524,903],{"emptyLinePlaceholder":902},[358,1526,1528,1530,1532,1535,1538,1540,1543,1545,1548,1550,1553,1556],{"class":360,"line":1527},40,[358,1529,961],{"class":364},[358,1531,1201],{"class":372},[358,1533,1534],{"class":368},"{",[358,1536,1537],{"class":492},"AccessToken",[358,1539,712],{"class":368},[358,1541,1542],{"class":492}," access",[358,1544,930],{"class":368},[358,1546,1547],{"class":492}," RefreshToken",[358,1549,712],{"class":368},[358,1551,1552],{"class":492}," refresh",[358,1554,1555],{"class":368},"},",[358,1557,1558],{"class":368}," nil\n",[358,1560,1562],{"class":360,"line":1561},41,[358,1563,558],{"class":368},[665,1565,1566,1567,1569,1570,1573,1574,1576,1577,1580],{},"Refresh token handling is your schema: ",[355,1568,1372],{}," only returns a cryptographically random string, the kit does not store it. Server-side, store ",[386,1571,1572],{},"only its hash"," (as with passwords), and use ",[355,1575,627],{}," as the expiry (the ",[355,1578,1579],{},"sha256Hex"," above is your own helper).",[444,1582,1584],{"id":1583},"the-middleware","The middleware",[378,1586,1587,1588,1591,1592,1594,1595,1598,1599,1602,1603,1606,1607,499],{},"The middleware performs three steps on every request: it extracts the Bearer token from the ",[355,1589,1590],{},"Authorization"," header, validates it with the ",[355,1593,817],{}," (signature, expiry, issuer\u002Faudience), then puts the ",[355,1596,1597],{},"*rbac.Identity"," built from the claims into the context. A failure at any step yields a ",[386,1600,1601],{},"401"," ",[399,1604,1605],{"href":119},"problem+json"," response; code running after the middleware reads the caller back with ",[399,1608,1609],{"href":209},[355,1610,1611],{},"rbac.FromContext",[378,1613,1614,1615,1618,1619,1622,1623,1625],{},"This is how the shop's ",[355,1616,1617],{},"admin"," surface goes behind JWT + role protection: you add the middleware inside the generated ",[355,1620,1621],{},"Register\u003CSurface>"," function, without touching the other surfaces (see ",[399,1624,314],{"href":315},"):",[1627,1628,1629,1825],"code-group",{},[348,1630,1633],{"className":350,"code":1631,"filename":1632,"language":352,"meta":353,"style":353},"func RegisterAdmin(router fiber.Router, deps Dependencies) {\n    adminSvc := adminservice.New()\n    adminHandler := adminhttp.New(adminSvc)\n    adminPolicies := adminpolicy.New()\n    adminGroup := router.Group(\"\u002Fadmin\u002Fshop\")\n    adminGroup.Use(auth.Middleware(deps.Auth), rbac.RequireRole(\"admin\"))\n    shopadmingen.RegisterHandlersWithOptions(adminGroup, \u002F* ... generated strict-handler wiring unchanged ... *\u002F)\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go (Fiber)",[355,1634,1635,1667,1685,1706,1722,1749,1799,1821],{"__ignoreMap":353},[358,1636,1637,1639,1642,1644,1647,1650,1652,1655,1657,1660,1663,1665],{"class":360,"line":361},[358,1638,909],{"class":368},[358,1640,1641],{"class":912}," RegisterAdmin",[358,1643,916],{"class":368},[358,1645,1646],{"class":919},"router",[358,1648,1649],{"class":372}," fiber",[358,1651,499],{"class":368},[358,1653,1654],{"class":372},"Router",[358,1656,930],{"class":368},[358,1658,1659],{"class":919}," deps",[358,1661,1662],{"class":372}," Dependencies",[358,1664,947],{"class":368},[358,1666,486],{"class":368},[358,1668,1669,1672,1674,1677,1679,1682],{"class":360,"line":474},[358,1670,1671],{"class":492},"    adminSvc ",[358,1673,1131],{"class":368},[358,1675,1676],{"class":492}," adminservice",[358,1678,499],{"class":368},[358,1680,1681],{"class":912},"New",[358,1683,1684],{"class":368},"()\n",[358,1686,1687,1690,1692,1695,1697,1699,1701,1704],{"class":360,"line":489},[358,1688,1689],{"class":492},"    adminHandler ",[358,1691,1131],{"class":368},[358,1693,1694],{"class":492}," adminhttp",[358,1696,499],{"class":368},[358,1698,1681],{"class":912},[358,1700,916],{"class":368},[358,1702,1703],{"class":492},"adminSvc",[358,1705,1155],{"class":368},[358,1707,1708,1711,1713,1716,1718,1720],{"class":360,"line":505},[358,1709,1710],{"class":492},"    adminPolicies ",[358,1712,1131],{"class":368},[358,1714,1715],{"class":492}," adminpolicy",[358,1717,499],{"class":368},[358,1719,1681],{"class":912},[358,1721,1684],{"class":368},[358,1723,1724,1727,1729,1732,1734,1737,1739,1742,1745,1747],{"class":360,"line":529},[358,1725,1726],{"class":492},"    adminGroup ",[358,1728,1131],{"class":368},[358,1730,1731],{"class":492}," router",[358,1733,499],{"class":368},[358,1735,1736],{"class":912},"Group",[358,1738,916],{"class":368},[358,1740,1741],{"class":368},"\"",[358,1743,1744],{"class":522},"\u002Fadmin\u002Fshop",[358,1746,1741],{"class":368},[358,1748,1155],{"class":368},[358,1750,1751,1754,1756,1759,1761,1763,1765,1768,1770,1773,1775,1778,1780,1783,1785,1788,1790,1792,1794,1796],{"class":360,"line":549},[358,1752,1753],{"class":492},"    adminGroup",[358,1755,499],{"class":368},[358,1757,1758],{"class":912},"Use",[358,1760,916],{"class":368},[358,1762,383],{"class":492},[358,1764,499],{"class":368},[358,1766,1767],{"class":912},"Middleware",[358,1769,916],{"class":368},[358,1771,1772],{"class":492},"deps",[358,1774,499],{"class":368},[358,1776,1777],{"class":492},"Auth",[358,1779,1489],{"class":368},[358,1781,1782],{"class":492}," rbac",[358,1784,499],{"class":368},[358,1786,1787],{"class":912},"RequireRole",[358,1789,916],{"class":368},[358,1791,1741],{"class":368},[358,1793,1617],{"class":522},[358,1795,1741],{"class":368},[358,1797,1798],{"class":368},"))\n",[358,1800,1801,1804,1806,1809,1811,1814,1816,1819],{"class":360,"line":555},[358,1802,1803],{"class":492},"    shopadmingen",[358,1805,499],{"class":368},[358,1807,1808],{"class":912},"RegisterHandlersWithOptions",[358,1810,916],{"class":368},[358,1812,1813],{"class":492},"adminGroup",[358,1815,930],{"class":368},[358,1817,1818],{"class":470}," \u002F* ... generated strict-handler wiring unchanged ... *\u002F",[358,1820,1155],{"class":368},[358,1822,1823],{"class":360,"line":899},[358,1824,558],{"class":368},[348,1826,1829],{"className":350,"code":1827,"filename":1828,"language":352,"meta":353,"style":353},"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(auth.MiddlewareHTTP(deps.Auth), rbac.RequireRoleHTTP(\"admin\"))\n        shopadmingen.HandlerWithOptions(\u002F* ... generated strict-handler wiring unchanged ... *\u002F)\n    })\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go (chi)",[355,1830,1831,1858,1872,1890,1904,1940,1985,2002,2007],{"__ignoreMap":353},[358,1832,1833,1835,1837,1839,1841,1844,1846,1848,1850,1852,1854,1856],{"class":360,"line":361},[358,1834,909],{"class":368},[358,1836,1641],{"class":912},[358,1838,916],{"class":368},[358,1840,1646],{"class":919},[358,1842,1843],{"class":372}," chi",[358,1845,499],{"class":368},[358,1847,1654],{"class":372},[358,1849,930],{"class":368},[358,1851,1659],{"class":919},[358,1853,1662],{"class":372},[358,1855,947],{"class":368},[358,1857,486],{"class":368},[358,1859,1860,1862,1864,1866,1868,1870],{"class":360,"line":474},[358,1861,1671],{"class":492},[358,1863,1131],{"class":368},[358,1865,1676],{"class":492},[358,1867,499],{"class":368},[358,1869,1681],{"class":912},[358,1871,1684],{"class":368},[358,1873,1874,1876,1878,1880,1882,1884,1886,1888],{"class":360,"line":489},[358,1875,1689],{"class":492},[358,1877,1131],{"class":368},[358,1879,1694],{"class":492},[358,1881,499],{"class":368},[358,1883,1681],{"class":912},[358,1885,916],{"class":368},[358,1887,1703],{"class":492},[358,1889,1155],{"class":368},[358,1891,1892,1894,1896,1898,1900,1902],{"class":360,"line":505},[358,1893,1710],{"class":492},[358,1895,1131],{"class":368},[358,1897,1715],{"class":492},[358,1899,499],{"class":368},[358,1901,1681],{"class":912},[358,1903,1684],{"class":368},[358,1905,1906,1909,1911,1914,1916,1918,1920,1922,1924,1927,1930,1932,1934,1936,1938],{"class":360,"line":529},[358,1907,1908],{"class":492},"    router",[358,1910,499],{"class":368},[358,1912,1913],{"class":912},"Route",[358,1915,916],{"class":368},[358,1917,1741],{"class":368},[358,1919,1744],{"class":522},[358,1921,1741],{"class":368},[358,1923,930],{"class":368},[358,1925,1926],{"class":368}," func(",[358,1928,1929],{"class":919},"r",[358,1931,1843],{"class":372},[358,1933,499],{"class":368},[358,1935,1654],{"class":372},[358,1937,947],{"class":368},[358,1939,486],{"class":368},[358,1941,1942,1945,1947,1949,1951,1953,1955,1958,1960,1962,1964,1966,1968,1970,1972,1975,1977,1979,1981,1983],{"class":360,"line":549},[358,1943,1944],{"class":492},"        r",[358,1946,499],{"class":368},[358,1948,1758],{"class":912},[358,1950,916],{"class":368},[358,1952,383],{"class":492},[358,1954,499],{"class":368},[358,1956,1957],{"class":912},"MiddlewareHTTP",[358,1959,916],{"class":368},[358,1961,1772],{"class":492},[358,1963,499],{"class":368},[358,1965,1777],{"class":492},[358,1967,1489],{"class":368},[358,1969,1782],{"class":492},[358,1971,499],{"class":368},[358,1973,1974],{"class":912},"RequireRoleHTTP",[358,1976,916],{"class":368},[358,1978,1741],{"class":368},[358,1980,1617],{"class":522},[358,1982,1741],{"class":368},[358,1984,1798],{"class":368},[358,1986,1987,1990,1992,1995,1997,2000],{"class":360,"line":555},[358,1988,1989],{"class":492},"        shopadmingen",[358,1991,499],{"class":368},[358,1993,1994],{"class":912},"HandlerWithOptions",[358,1996,916],{"class":368},[358,1998,1999],{"class":470},"\u002F* ... generated strict-handler wiring unchanged ... *\u002F",[358,2001,1155],{"class":368},[358,2003,2004],{"class":360,"line":899},[358,2005,2006],{"class":368},"    })\n",[358,2008,2009],{"class":360,"line":906},[358,2010,558],{"class":368},[378,2012,2013,2014,2016,2017,2020,2021,2024,2025,2028,2029,2032,2033,2036,2037,2040],{},"You pass ",[355,2015,451],{}," in through the module's ",[355,2018,2019],{},"Dependencies"," struct (an ",[355,2022,2023],{},"Auth auth.Config"," field at the ",[355,2026,2027],{},"\u002F\u002F gpsystem:dependencies"," anchor, ",[355,2030,2031],{},"Auth: cfg.JWT"," in ",[355,2034,2035],{},"main.go","). This way ",[355,2038,2039],{},"register.go"," never depends on the platform config.",[2042,2043,2045],"h3",{"id":2044},"mixed-surfaces-public-and-protected-operations-side-by-side","Mixed surfaces: public and protected operations side by side",[378,2047,2048,2049,2052,2053,2055,2056,2059,2060,2063],{},"The middleware rejects ",[386,2050,2051],{},"every"," tokenless request with 401; for an all-protected surface (like ",[355,2054,1617],{},") that is exactly what you want. The shop's ",[355,2057,2058],{},"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 ",[399,2061,2062],{"href":214},"policy enforcer",", which rejects without an identity anyway:",[1627,2065,2066,2196],{},[348,2067,2070],{"className":350,"code":2068,"filename":2069,"language":352,"meta":353,"style":353},"authOptional := auth.Middleware(deps.Auth)\napiGroup.Use(func(c fiber.Ctx) error {\n    if c.Get(fiber.HeaderAuthorization) == \"\" {\n        return c.Next() \u002F\u002F anonymous browsing passes; tagged operations get 401 from the enforcer\n    }\n    return authOptional(c) \u002F\u002F if a token is present, it must be valid\n})\n","Fiber",[355,2071,2072,2095,2123,2155,2171,2175,2191],{"__ignoreMap":353},[358,2073,2074,2077,2079,2081,2083,2085,2087,2089,2091,2093],{"class":360,"line":361},[358,2075,2076],{"class":492},"authOptional ",[358,2078,1131],{"class":368},[358,2080,923],{"class":492},[358,2082,499],{"class":368},[358,2084,1767],{"class":912},[358,2086,916],{"class":368},[358,2088,1772],{"class":492},[358,2090,499],{"class":368},[358,2092,1777],{"class":492},[358,2094,1155],{"class":368},[358,2096,2097,2100,2102,2104,2107,2110,2112,2114,2117,2119,2121],{"class":360,"line":474},[358,2098,2099],{"class":492},"apiGroup",[358,2101,499],{"class":368},[358,2103,1758],{"class":912},[358,2105,2106],{"class":368},"(func(",[358,2108,2109],{"class":919},"c",[358,2111,1649],{"class":372},[358,2113,499],{"class":368},[358,2115,2116],{"class":372},"Ctx",[358,2118,947],{"class":368},[358,2120,1113],{"class":736},[358,2122,486],{"class":368},[358,2124,2125,2127,2130,2132,2135,2137,2140,2142,2145,2147,2150,2153],{"class":360,"line":489},[358,2126,1161],{"class":364},[358,2128,2129],{"class":492}," c",[358,2131,499],{"class":368},[358,2133,2134],{"class":912},"Get",[358,2136,916],{"class":368},[358,2138,2139],{"class":492},"fiber",[358,2141,499],{"class":368},[358,2143,2144],{"class":492},"HeaderAuthorization",[358,2146,947],{"class":368},[358,2148,2149],{"class":368}," ==",[358,2151,2152],{"class":368}," \"\"",[358,2154,486],{"class":368},[358,2156,2157,2159,2161,2163,2166,2168],{"class":360,"line":505},[358,2158,1198],{"class":364},[358,2160,2129],{"class":492},[358,2162,499],{"class":368},[358,2164,2165],{"class":912},"Next",[358,2167,1375],{"class":368},[358,2169,2170],{"class":470}," \u002F\u002F anonymous browsing passes; tagged operations get 401 from the enforcer\n",[358,2172,2173],{"class":360,"line":529},[358,2174,1045],{"class":368},[358,2176,2177,2179,2182,2184,2186,2188],{"class":360,"line":549},[358,2178,961],{"class":364},[358,2180,2181],{"class":912}," authOptional",[358,2183,916],{"class":368},[358,2185,2109],{"class":492},[358,2187,947],{"class":368},[358,2189,2190],{"class":470}," \u002F\u002F if a token is present, it must be valid\n",[358,2192,2193],{"class":360,"line":555},[358,2194,2195],{"class":368},"})\n",[348,2197,2200],{"className":350,"code":2198,"filename":2199,"language":352,"meta":353,"style":353},"authOptional := auth.MiddlewareHTTP(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\n            return\n        }\n        authOptional(next).ServeHTTP(w, r) \u002F\u002F if a token is present, it must be valid\n    })\n})\n","chi",[355,2201,2202,2224,2255,2297,2329,2352,2357,2362,2388,2392],{"__ignoreMap":353},[358,2203,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222],{"class":360,"line":361},[358,2205,2076],{"class":492},[358,2207,1131],{"class":368},[358,2209,923],{"class":492},[358,2211,499],{"class":368},[358,2213,1957],{"class":912},[358,2215,916],{"class":368},[358,2217,1772],{"class":492},[358,2219,499],{"class":368},[358,2221,1777],{"class":492},[358,2223,1155],{"class":368},[358,2225,2226,2228,2230,2232,2234,2237,2240,2242,2245,2247,2249,2251,2253],{"class":360,"line":474},[358,2227,1929],{"class":492},[358,2229,499],{"class":368},[358,2231,1758],{"class":912},[358,2233,2106],{"class":368},[358,2235,2236],{"class":919},"next",[358,2238,2239],{"class":372}," http",[358,2241,499],{"class":368},[358,2243,2244],{"class":372},"Handler",[358,2246,947],{"class":368},[358,2248,2239],{"class":372},[358,2250,499],{"class":368},[358,2252,2244],{"class":372},[358,2254,486],{"class":368},[358,2256,2257,2259,2261,2263,2266,2268,2271,2273,2275,2278,2280,2283,2285,2288,2290,2293,2295],{"class":360,"line":489},[358,2258,961],{"class":364},[358,2260,2239],{"class":492},[358,2262,499],{"class":368},[358,2264,2265],{"class":912},"HandlerFunc",[358,2267,2106],{"class":368},[358,2269,2270],{"class":919},"w",[358,2272,2239],{"class":372},[358,2274,499],{"class":368},[358,2276,2277],{"class":372},"ResponseWriter",[358,2279,930],{"class":368},[358,2281,2282],{"class":919}," r",[358,2284,950],{"class":368},[358,2286,2287],{"class":372},"http",[358,2289,499],{"class":368},[358,2291,2292],{"class":372},"Request",[358,2294,947],{"class":368},[358,2296,486],{"class":368},[358,2298,2299,2302,2304,2306,2309,2311,2313,2315,2317,2319,2321,2323,2325,2327],{"class":360,"line":505},[358,2300,2301],{"class":364},"        if",[358,2303,2282],{"class":492},[358,2305,499],{"class":368},[358,2307,2308],{"class":492},"Header",[358,2310,499],{"class":368},[358,2312,2134],{"class":912},[358,2314,916],{"class":368},[358,2316,1741],{"class":368},[358,2318,1590],{"class":522},[358,2320,1741],{"class":368},[358,2322,947],{"class":368},[358,2324,2149],{"class":368},[358,2326,2152],{"class":368},[358,2328,486],{"class":368},[358,2330,2331,2334,2336,2339,2341,2343,2345,2347,2349],{"class":360,"line":529},[358,2332,2333],{"class":492},"            next",[358,2335,499],{"class":368},[358,2337,2338],{"class":912},"ServeHTTP",[358,2340,916],{"class":368},[358,2342,2270],{"class":492},[358,2344,930],{"class":368},[358,2346,2282],{"class":492},[358,2348,947],{"class":368},[358,2350,2351],{"class":470}," \u002F\u002F anonymous browsing passes\n",[358,2353,2354],{"class":360,"line":549},[358,2355,2356],{"class":364},"            return\n",[358,2358,2359],{"class":360,"line":555},[358,2360,2361],{"class":368},"        }\n",[358,2363,2364,2367,2369,2371,2374,2376,2378,2380,2382,2384,2386],{"class":360,"line":899},[358,2365,2366],{"class":912},"        authOptional",[358,2368,916],{"class":368},[358,2370,2236],{"class":492},[358,2372,2373],{"class":368},").",[358,2375,2338],{"class":912},[358,2377,916],{"class":368},[358,2379,2270],{"class":492},[358,2381,930],{"class":368},[358,2383,2282],{"class":492},[358,2385,947],{"class":368},[358,2387,2190],{"class":470},[358,2389,2390],{"class":360,"line":906},[358,2391,2006],{"class":368},[358,2393,2394],{"class":360,"line":958},[358,2395,2195],{"class":368},[444,2397,2399],{"id":2398},"custom-claims","Custom claims",[378,2401,2402,2403,2405,2406,2409,2410,2413,2414,2416],{},"When ",[355,2404,705],{}," is not enough (say you carry a tenant identifier in the token), define your own claims type and use the ",[355,2407,2408],{},"MiddlewareFor"," \u002F ",[355,2411,2412],{},"MiddlewareForHTTP"," pair to state how an ",[355,2415,415],{}," is built from it:",[348,2418,2420],{"className":350,"code":2419,"language":352,"meta":353,"style":353},"type MyClaims struct {\n    TenantID string `json:\"tenant\"`\n    jwt.RegisteredClaims\n}\n\nissuer := auth.NewTokenIssuer[MyClaims](cfg.JWT)\nmw := auth.MiddlewareFor(issuer, 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",[355,2421,2422,2433,2447,2455,2459,2463,2493,2538,2552,2568,2607,2614],{"__ignoreMap":353},[358,2423,2424,2426,2429,2431],{"class":360,"line":361},[358,2425,477],{"class":368},[358,2427,2428],{"class":372}," MyClaims",[358,2430,483],{"class":368},[358,2432,486],{"class":368},[358,2434,2435,2438,2440,2442,2445],{"class":360,"line":474},[358,2436,2437],{"class":492},"    TenantID ",[358,2439,737],{"class":736},[358,2441,541],{"class":368},[358,2443,2444],{"class":522},"json:\"tenant\"",[358,2446,526],{"class":368},[358,2448,2449,2451,2453],{"class":360,"line":489},[358,2450,783],{"class":372},[358,2452,499],{"class":368},[358,2454,788],{"class":372},[358,2456,2457],{"class":360,"line":505},[358,2458,558],{"class":368},[358,2460,2461],{"class":360,"line":529},[358,2462,903],{"emptyLinePlaceholder":902},[358,2464,2465,2468,2470,2472,2474,2476,2478,2481,2483,2486,2488,2491],{"class":360,"line":549},[358,2466,2467],{"class":492},"issuer ",[358,2469,1131],{"class":368},[358,2471,923],{"class":492},[358,2473,499],{"class":368},[358,2475,998],{"class":912},[358,2477,867],{"class":368},[358,2479,2480],{"class":372},"MyClaims",[358,2482,1009],{"class":368},[358,2484,2485],{"class":492},"cfg",[358,2487,499],{"class":368},[358,2489,2490],{"class":492},"JWT",[358,2492,1155],{"class":368},[358,2494,2495,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2523,2526,2528,2530,2532,2534,2536],{"class":360,"line":555},[358,2496,2497],{"class":492},"mw ",[358,2499,1131],{"class":368},[358,2501,923],{"class":492},[358,2503,499],{"class":368},[358,2505,2408],{"class":912},[358,2507,916],{"class":368},[358,2509,1306],{"class":492},[358,2511,930],{"class":368},[358,2513,1926],{"class":368},[358,2515,2109],{"class":919},[358,2517,2428],{"class":372},[358,2519,947],{"class":368},[358,2521,2522],{"class":368}," (*",[358,2524,2525],{"class":372},"rbac",[358,2527,499],{"class":368},[358,2529,415],{"class":372},[358,2531,930],{"class":368},[358,2533,1113],{"class":736},[358,2535,947],{"class":368},[358,2537,486],{"class":368},[358,2539,2540,2542,2544,2546,2548,2550],{"class":360,"line":899},[358,2541,961],{"class":364},[358,2543,964],{"class":368},[358,2545,2525],{"class":372},[358,2547,499],{"class":368},[358,2549,415],{"class":372},[358,2551,969],{"class":368},[358,2553,2554,2557,2559,2561,2563,2566],{"class":360,"line":906},[358,2555,2556],{"class":492},"        Subject",[358,2558,712],{"class":368},[358,2560,2129],{"class":492},[358,2562,499],{"class":368},[358,2564,2565],{"class":492},"Subject",[358,2567,983],{"class":368},[358,2569,2570,2573,2575,2578,2580,2583,2586,2588,2590,2593,2595,2597,2599,2601,2604],{"class":360,"line":958},[358,2571,2572],{"class":492},"        Extra",[358,2574,712],{"class":368},[358,2576,2577],{"class":368},"   map[",[358,2579,737],{"class":736},[358,2581,2582],{"class":368},"]",[358,2584,2585],{"class":372},"any",[358,2587,1534],{"class":368},[358,2589,1741],{"class":368},[358,2591,2592],{"class":522},"tenant",[358,2594,1741],{"class":368},[358,2596,712],{"class":368},[358,2598,2129],{"class":492},[358,2600,499],{"class":368},[358,2602,2603],{"class":492},"TenantID",[358,2605,2606],{"class":368},"},\n",[358,2608,2609,2612],{"class":360,"line":972},[358,2610,2611],{"class":368},"    },",[358,2613,1558],{"class":368},[358,2615,2616],{"class":360,"line":986},[358,2617,2195],{"class":368},[378,2619,2620,2622,2623,2626,2627,683,2629,2631,2632,2634,2635,2637],{},[355,2621,2412],{}," is the same in ",[355,2624,2625],{},"func(http.Handler) http.Handler"," shape; the built-in ",[355,2628,1767],{},[355,2630,1957],{}," are exactly this pair, pre-wired with ",[355,2633,705],{},". The JWT parsing and the ",[355,2636,415],{}," contract are shared between the two engines.",[444,2639,2641],{"id":2640},"hs256-and-secret-management","HS256 and secret management",[378,2643,2644,2645,2648,2649,2652,2653,2656,2657,2659],{},"Signing is ",[386,2646,2647],{},"HS256"," (shared secret). Parsing pins the algorithm (",[355,2650,2651],{},"WithValidMethods","), requires an expiry (",[355,2654,2655],{},"WithExpirationRequired","), and when configured, checks issuer and audience, so alg-confusion attacks and unsigned tokens are rejected. The ",[355,2658,817],{}," API is claims-generic, so asymmetric algorithms can be added later without breaking consumers.",[378,2661,2662,2663,2666,2667,2670,2671,2673],{},"The secret should be long and random (e.g. ",[355,2664,2665],{},"openssl rand -hex 32",") and come from a secret manager: never commit it to the repo. ",[355,2668,2669],{},".env"," is for local development only; see the ",[399,2672,446],{"href":83}," page.",[378,2675,2676,2677,2679,2680,2682,2683,499],{},"The next layer: ",[399,2678,407],{"href":209}," over the ",[355,2681,415],{}," the token became, and per-request ",[399,2684,411],{"href":214},[444,2686,2688],{"id":2687},"patterns-used","Patterns used",[378,2690,2691,1063,2694,802,2697,683,2700,2703,2704,499],{},[386,2692,2693],{},"Generics over the claims type",[355,2695,2696],{},"TokenIssuer[C jwt.Claims]",[355,2698,2699],{},"MiddlewareFor[C]",[355,2701,2702],{},"MiddlewareForHTTP[C]","): ",[399,2705,92],{"href":2706},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#generics-over-the-claims-type",[2708,2709,2710],"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 .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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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":353,"searchDepth":474,"depth":474,"links":2712},[2713,2714,2715,2718,2719,2720],{"id":446,"depth":474,"text":52},{"id":698,"depth":474,"text":699},{"id":1583,"depth":474,"text":1584,"children":2716},[2717],{"id":2044,"depth":489,"text":2045},{"id":2398,"depth":474,"text":2399},{"id":2640,"depth":474,"text":2641},{"id":2687,"depth":474,"text":2688},"JWT issuing and the Bearer middleware: stateless auth.","md",null,{},{"icon":206},{"title":203,"description":2721},"fbGlU5TN2rXPp5ZZM2F8MAWUxqfuJH1MwEAXe2xgEc4",[2729,2731],{"title":192,"path":193,"stem":194,"description":2730,"icon":195,"children":-1},"The background-work chassis, asynq server, outbox relay and schedule runner in one binary, under a shared graceful shutdown.",{"title":208,"path":209,"stem":210,"description":2732,"icon":211,"children":-1},"Role and permission checks over the Identity carried in the context: with route guards and service-layer queries.",1784668709911]