[{"data":1,"prerenderedAt":1994},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fpackages\u002Fauth\u002Flocal":176,"\u002Fdocs\u002Fpackages\u002Fauth\u002Flocal-surround":1989},[4,21,86,158],{"title":5,"path":6,"stem":7,"children":8,"status":11,"icon":20},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started\u002F1.index",[9,12,16],{"title":10,"path":6,"stem":7,"status":11},"Introduction",null,{"title":13,"path":14,"stem":15,"status":11},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","1.docs\u002F1.getting-started\u002F2.installation",{"title":17,"path":18,"stem":19,"status":11},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","1.docs\u002F1.getting-started\u002F3.quick-start",false,{"title":22,"path":23,"stem":24,"children":25,"status":11,"icon":20},"Core Concepts","\u002Fdocs\u002Fcore-concepts","1.docs\u002F2.core-concepts\u002F1.index",[26,28,32,36,46,50,54,58,62,66,70,74,78,82],{"title":27,"path":23,"stem":24,"status":11},"Overview",{"title":29,"path":30,"stem":31,"status":11},"Response","\u002Fdocs\u002Fcore-concepts\u002Fresponse","1.docs\u002F2.core-concepts\u002F10.response",{"title":33,"path":34,"stem":35,"status":11},"Testing","\u002Fdocs\u002Fcore-concepts\u002Ftesting","1.docs\u002F2.core-concepts\u002F12.testing",{"title":37,"path":38,"stem":39,"children":40,"status":11,"icon":20,"defaultOpen":20},"Decorators","\u002Fdocs\u002Fcore-concepts\u002Fdecorators","1.docs\u002F2.core-concepts\u002F13.decorators\u002F1.index",[41,42],{"title":27,"path":38,"stem":39,"status":11},{"title":43,"path":44,"stem":45,"status":11},"Custom","\u002Fdocs\u002Fcore-concepts\u002Fdecorators\u002Fcustom","1.docs\u002F2.core-concepts\u002F13.decorators\u002F2.custom",{"title":47,"path":48,"stem":49,"status":11},"Discovery Service","\u002Fdocs\u002Fcore-concepts\u002Fdiscovery","1.docs\u002F2.core-concepts\u002F14.discovery",{"title":51,"path":52,"stem":53,"status":11},"Application Lifecycle","\u002Fdocs\u002Fcore-concepts\u002Fapp-lifecycle","1.docs\u002F2.core-concepts\u002F15.app-lifecycle",{"title":55,"path":56,"stem":57,"status":11},"Controllers","\u002Fdocs\u002Fcore-concepts\u002Fcontrollers","1.docs\u002F2.core-concepts\u002F2.controllers",{"title":59,"path":60,"stem":61,"status":11},"Routing","\u002Fdocs\u002Fcore-concepts\u002Frouting","1.docs\u002F2.core-concepts\u002F3.routing",{"title":63,"path":64,"stem":65,"status":11},"Providers","\u002Fdocs\u002Fcore-concepts\u002Fproviders","1.docs\u002F2.core-concepts\u002F4.providers",{"title":67,"path":68,"stem":69,"status":11},"Modules","\u002Fdocs\u002Fcore-concepts\u002Fmodules","1.docs\u002F2.core-concepts\u002F5.modules",{"title":71,"path":72,"stem":73,"status":11},"Configuration","\u002Fdocs\u002Fcore-concepts\u002Fconfiguration","1.docs\u002F2.core-concepts\u002F6.configuration",{"title":75,"path":76,"stem":77,"status":11},"Middleware","\u002Fdocs\u002Fcore-concepts\u002Fmiddleware","1.docs\u002F2.core-concepts\u002F7.middleware",{"title":79,"path":80,"stem":81,"status":11},"Guards","\u002Fdocs\u002Fcore-concepts\u002Fguards","1.docs\u002F2.core-concepts\u002F8.guards",{"title":83,"path":84,"stem":85,"status":11},"Exceptions","\u002Fdocs\u002Fcore-concepts\u002Fexceptions","1.docs\u002F2.core-concepts\u002F9.exceptions",{"title":87,"path":88,"stem":89,"children":90,"status":11,"icon":20},"Packages","\u002Fdocs\u002Fpackages","1.docs\u002F3.packages\u002F1.index",[91,92,97,108,112,130,134,138,142,146,150,154],{"title":27,"path":88,"stem":89,"status":11},{"title":93,"path":94,"stem":95,"status":96},"CLI","\u002Fdocs\u002Fpackages\u002Fcli","1.docs\u002F3.packages\u002F10.cli","experimental",{"title":98,"path":99,"stem":100,"children":101,"status":11,"icon":20,"defaultOpen":20},"Events","\u002Fdocs\u002Fpackages\u002Fmessaging","1.docs\u002F3.packages\u002F11.messaging\u002F1.index",[102,104],{"title":27,"path":99,"stem":100,"status":103},"beta",{"title":105,"path":106,"stem":107,"status":96},"Redis","\u002Fdocs\u002Fpackages\u002Fmessaging\u002Fredis","1.docs\u002F3.packages\u002F11.messaging\u002F2.redis",{"title":109,"path":110,"stem":111,"status":103},"Serve Static","\u002Fdocs\u002Fpackages\u002Fserve-static","1.docs\u002F3.packages\u002F12.serve-static",{"title":113,"path":114,"stem":115,"children":116,"status":11,"icon":20,"defaultOpen":20},"Auth","\u002Fdocs\u002Fpackages\u002Fauth","1.docs\u002F3.packages\u002F2.auth\u002F1.index",[117,118,122,126],{"title":27,"path":114,"stem":115,"status":103},{"title":119,"path":120,"stem":121,"status":103},"JWT Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Fjwt","1.docs\u002F3.packages\u002F2.auth\u002F2.jwt",{"title":123,"path":124,"stem":125,"status":103},"Local Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Flocal","1.docs\u002F3.packages\u002F2.auth\u002F3.local",{"title":127,"path":128,"stem":129,"status":96},"OAuth2 Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Foauth2","1.docs\u002F3.packages\u002F2.auth\u002F4.oauth2",{"title":131,"path":132,"stem":133,"status":103},"JWT","\u002Fdocs\u002Fpackages\u002Fjwt","1.docs\u002F3.packages\u002F3.jwt",{"title":135,"path":136,"stem":137,"status":103},"Drizzle","\u002Fdocs\u002Fpackages\u002Fdrizzle","1.docs\u002F3.packages\u002F4.drizzle",{"title":139,"path":140,"stem":141,"status":103},"Papr","\u002Fdocs\u002Fpackages\u002Fpapr","1.docs\u002F3.packages\u002F5.papr",{"title":143,"path":144,"stem":145,"status":103},"Mongoose","\u002Fdocs\u002Fpackages\u002Fmongoose","1.docs\u002F3.packages\u002F6.mongoose",{"title":147,"path":148,"stem":149,"status":103},"Swagger","\u002Fdocs\u002Fpackages\u002Fswagger","1.docs\u002F3.packages\u002F7.swagger",{"title":151,"path":152,"stem":153,"status":103},"Node Server","\u002Fdocs\u002Fpackages\u002Fnode-server","1.docs\u002F3.packages\u002F8.node-server",{"title":155,"path":156,"stem":157,"status":103},"uWS Server","\u002Fdocs\u002Fpackages\u002Fuws-server","1.docs\u002F3.packages\u002F9.uws-server",{"title":159,"path":160,"stem":161,"children":162,"status":11,"icon":20},"Roadmap","\u002Fdocs\u002Froadmap","1.docs\u002F4.roadmap\u002F1.index",[163,164,168,172],{"title":27,"path":160,"stem":161,"status":11},{"title":165,"path":166,"stem":167,"status":11},"Short term (0-3 months)","\u002Fdocs\u002Froadmap\u002Fshort-term","1.docs\u002F4.roadmap\u002F2.short-term",{"title":169,"path":170,"stem":171,"status":11},"Mid term (3-9 months)","\u002Fdocs\u002Froadmap\u002Fmid-term","1.docs\u002F4.roadmap\u002F3.mid-term",{"title":173,"path":174,"stem":175,"status":11},"Long term (9-12+ months)","\u002Fdocs\u002Froadmap\u002Flong-term","1.docs\u002F4.roadmap\u002F4.long-term",{"id":177,"title":123,"body":178,"description":1984,"extension":1985,"meta":1986,"navigation":467,"path":124,"seo":1987,"status":103,"stem":125,"__hash__":1988},"docs\u002F1.docs\u002F3.packages\u002F2.auth\u002F3.local.md",{"type":179,"value":180,"toc":1966},"minimark",[181,186,203,211,217,255,262,292,298,302,981,993,1007,1011,1018,1205,1212,1216,1225,1666,1682,1700,1704,1850,1854,1869,1873,1878,1888,1892,1900,1904,1924,1928,1962],[182,183,185],"h2",{"id":184},"problem","Problem",[187,188,189,190,194,195,198,199,202],"p",{},"You want a ",[191,192,193],"code",{},"POST \u002Fauth\u002Flogin"," endpoint that accepts ",[191,196,197],{},"{ email, password }",", verifies the password against an argon2 hash in the database, and on success populates ",[191,200,201],{},"ctx.user"," with the matched user. The handler then issues a JWT.",[182,204,206,207,210],{"id":205},"why-an-authprovider-and-not-a-plain-controller","Why an ",[191,208,209],{},"AuthProvider"," and not a plain controller?",[187,212,213,214,216],{},"Both work. Putting the credential check in an ",[191,215,209],{}," gives you three things:",[218,219,220,232,242],"ol",{},[221,222,223,227,228,231],"li",{},[224,225,226],"strong",{},"Consistency"," - login looks like every other authenticated route (one ",[191,229,230],{},"@UseGuard"," line), so reading the controller is trivial.",[221,233,234,237,238,241],{},[224,235,236],{},"Composability"," - you can stack the local provider in a multi-provider guard (",[191,239,240],{},"AuthGuard(JwtAuth, LocalAuth)",") for endpoints that accept either.",[221,243,244,247,248,250,251,254],{},[224,245,246],{},"Reusability"," - the same provider works for ",[191,249,193],{}," and ",[191,252,253],{},"POST \u002Fauth\u002Frefresh-credentials"," without duplicating logic.",[182,256,258,259],{"id":257},"pipeline-note-provider-runs-before-validatebody","Pipeline note: provider runs before ",[191,260,261],{},"@ValidateBody",[187,263,264,265,268,269,272,273,276,277,279,280,283,284,287,288,291],{},"This is a ",[224,266,267],{},"critical"," ordering detail. MiiaJS executes guards (including ",[191,270,271],{},"AuthGuard",") ",[224,274,275],{},"before"," the handler - and ",[191,278,261],{}," runs as part of the handler's wrapping, not as a guard. So inside the provider's ",[191,281,282],{},"authenticate()",", ",[191,285,286],{},"ctx.json()"," returns the ",[224,289,290],{},"raw, unvalidated body",".",[187,293,294,295,297],{},"The provider must validate the body itself. Don't rely on ",[191,296,261],{}," to have run yet - it hasn't.",[182,299,301],{"id":300},"solution","Solution",[303,304,309],"pre",{"className":305,"code":306,"language":307,"meta":308,"style":308},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F src\u002Fauth\u002Fproviders\u002Flocal-auth.provider.ts\nimport { Injectable, inject, UnauthorizedException, type RequestContext } from '@miiajs\u002Fcore'\nimport type { AuthProvider } from '@miiajs\u002Fauth'\nimport { verify as verifyHash } from '@node-rs\u002Fargon2'\nimport { z } from 'zod'\nimport { UsersService } from '..\u002F..\u002Fusers\u002Fusers.service.js'\n\nconst LoginSchema = z.object({\n  email: z.string().email(),\n  password: z.string().min(1),\n})\n\n@Injectable()\nexport class LocalAuth implements AuthProvider {\n  private usersService = inject(UsersService)\n\n  async authenticate(ctx: RequestContext) {\n    const raw = await ctx.json().catch(() => null)\n    const parsed = LoginSchema.safeParse(raw)\n    if (!parsed.success) {\n      throw new UnauthorizedException('Invalid credentials payload')\n    }\n    const { email, password } = parsed.data\n\n    const found = await this.usersService.findByEmailForAuth(email)\n    if (!found) throw new UnauthorizedException('Invalid credentials')\n\n    const ok = await verifyHash(found.passwordHash, password)\n    if (!ok) throw new UnauthorizedException('Invalid credentials')\n\n    const { passwordHash: _, ...user } = found\n    return user\n  }\n}\n","typescript","",[191,310,311,320,370,393,420,441,462,469,496,526,558,567,572,584,605,622,627,650,691,716,740,763,769,795,800,829,861,866,895,925,930,960,969,975],{"__ignoreMap":308},[312,313,316],"span",{"class":314,"line":315},"line",1,[312,317,319],{"class":318},"sHwdD","\u002F\u002F src\u002Fauth\u002Fproviders\u002Flocal-auth.provider.ts\n",[312,321,323,327,331,335,338,341,343,346,348,351,354,357,360,363,367],{"class":314,"line":322},2,[312,324,326],{"class":325},"s7zQu","import",[312,328,330],{"class":329},"sMK4o"," {",[312,332,334],{"class":333},"sTEyZ"," Injectable",[312,336,337],{"class":329},",",[312,339,340],{"class":333}," inject",[312,342,337],{"class":329},[312,344,345],{"class":333}," UnauthorizedException",[312,347,337],{"class":329},[312,349,350],{"class":325}," type",[312,352,353],{"class":333}," RequestContext",[312,355,356],{"class":329}," }",[312,358,359],{"class":325}," from",[312,361,362],{"class":329}," '",[312,364,366],{"class":365},"sfazB","@miiajs\u002Fcore",[312,368,369],{"class":329},"'\n",[312,371,373,375,377,379,382,384,386,388,391],{"class":314,"line":372},3,[312,374,326],{"class":325},[312,376,350],{"class":325},[312,378,330],{"class":329},[312,380,381],{"class":333}," AuthProvider",[312,383,356],{"class":329},[312,385,359],{"class":325},[312,387,362],{"class":329},[312,389,390],{"class":365},"@miiajs\u002Fauth",[312,392,369],{"class":329},[312,394,396,398,400,403,406,409,411,413,415,418],{"class":314,"line":395},4,[312,397,326],{"class":325},[312,399,330],{"class":329},[312,401,402],{"class":333}," verify",[312,404,405],{"class":325}," as",[312,407,408],{"class":333}," verifyHash",[312,410,356],{"class":329},[312,412,359],{"class":325},[312,414,362],{"class":329},[312,416,417],{"class":365},"@node-rs\u002Fargon2",[312,419,369],{"class":329},[312,421,423,425,427,430,432,434,436,439],{"class":314,"line":422},5,[312,424,326],{"class":325},[312,426,330],{"class":329},[312,428,429],{"class":333}," z",[312,431,356],{"class":329},[312,433,359],{"class":325},[312,435,362],{"class":329},[312,437,438],{"class":365},"zod",[312,440,369],{"class":329},[312,442,444,446,448,451,453,455,457,460],{"class":314,"line":443},6,[312,445,326],{"class":325},[312,447,330],{"class":329},[312,449,450],{"class":333}," UsersService",[312,452,356],{"class":329},[312,454,359],{"class":325},[312,456,362],{"class":329},[312,458,459],{"class":365},"..\u002F..\u002Fusers\u002Fusers.service.js",[312,461,369],{"class":329},[312,463,465],{"class":314,"line":464},7,[312,466,468],{"emptyLinePlaceholder":467},true,"\n",[312,470,472,476,479,482,484,486,490,493],{"class":314,"line":471},8,[312,473,475],{"class":474},"spNyl","const",[312,477,478],{"class":333}," LoginSchema ",[312,480,481],{"class":329},"=",[312,483,429],{"class":333},[312,485,291],{"class":329},[312,487,489],{"class":488},"s2Zo4","object",[312,491,492],{"class":333},"(",[312,494,495],{"class":329},"{\n",[312,497,499,503,506,508,510,513,516,518,521,523],{"class":314,"line":498},9,[312,500,502],{"class":501},"swJcz","  email",[312,504,505],{"class":329},":",[312,507,429],{"class":333},[312,509,291],{"class":329},[312,511,512],{"class":488},"string",[312,514,515],{"class":333},"()",[312,517,291],{"class":329},[312,519,520],{"class":488},"email",[312,522,515],{"class":333},[312,524,525],{"class":329},",\n",[312,527,529,532,534,536,538,540,542,544,547,549,553,556],{"class":314,"line":528},10,[312,530,531],{"class":501},"  password",[312,533,505],{"class":329},[312,535,429],{"class":333},[312,537,291],{"class":329},[312,539,512],{"class":488},[312,541,515],{"class":333},[312,543,291],{"class":329},[312,545,546],{"class":488},"min",[312,548,492],{"class":333},[312,550,552],{"class":551},"sbssI","1",[312,554,555],{"class":333},")",[312,557,525],{"class":329},[312,559,561,564],{"class":314,"line":560},11,[312,562,563],{"class":329},"}",[312,565,566],{"class":333},")\n",[312,568,570],{"class":314,"line":569},12,[312,571,468],{"emptyLinePlaceholder":467},[312,573,575,578,581],{"class":314,"line":574},13,[312,576,577],{"class":329},"@",[312,579,580],{"class":488},"Injectable",[312,582,583],{"class":333},"()\n",[312,585,587,590,593,597,600,602],{"class":314,"line":586},14,[312,588,589],{"class":325},"export",[312,591,592],{"class":474}," class",[312,594,596],{"class":595},"sBMFI"," LocalAuth",[312,598,599],{"class":474}," implements",[312,601,381],{"class":595},[312,603,604],{"class":329}," {\n",[312,606,608,611,614,617,619],{"class":314,"line":607},15,[312,609,610],{"class":474},"  private",[312,612,613],{"class":501}," usersService",[312,615,616],{"class":329}," =",[312,618,340],{"class":501},[312,620,621],{"class":333},"(UsersService)\n",[312,623,625],{"class":314,"line":624},16,[312,626,468],{"emptyLinePlaceholder":467},[312,628,630,633,636,638,642,644,646,648],{"class":314,"line":629},17,[312,631,632],{"class":474},"  async",[312,634,635],{"class":501}," authenticate",[312,637,492],{"class":329},[312,639,641],{"class":640},"sHdIc","ctx",[312,643,505],{"class":329},[312,645,353],{"class":595},[312,647,555],{"class":329},[312,649,604],{"class":329},[312,651,653,656,659,661,664,667,669,672,674,676,679,681,683,686,689],{"class":314,"line":652},18,[312,654,655],{"class":474},"    const",[312,657,658],{"class":333}," raw",[312,660,616],{"class":329},[312,662,663],{"class":325}," await",[312,665,666],{"class":333}," ctx",[312,668,291],{"class":329},[312,670,671],{"class":488},"json",[312,673,515],{"class":501},[312,675,291],{"class":329},[312,677,678],{"class":488},"catch",[312,680,492],{"class":501},[312,682,515],{"class":329},[312,684,685],{"class":474}," =>",[312,687,688],{"class":329}," null",[312,690,566],{"class":501},[312,692,694,696,699,701,704,706,709,711,714],{"class":314,"line":693},19,[312,695,655],{"class":474},[312,697,698],{"class":333}," parsed",[312,700,616],{"class":329},[312,702,703],{"class":333}," LoginSchema",[312,705,291],{"class":329},[312,707,708],{"class":488},"safeParse",[312,710,492],{"class":501},[312,712,713],{"class":333},"raw",[312,715,566],{"class":501},[312,717,719,722,725,728,731,733,736,738],{"class":314,"line":718},20,[312,720,721],{"class":325},"    if",[312,723,724],{"class":501}," (",[312,726,727],{"class":329},"!",[312,729,730],{"class":333},"parsed",[312,732,291],{"class":329},[312,734,735],{"class":333},"success",[312,737,272],{"class":501},[312,739,495],{"class":329},[312,741,743,746,749,751,753,756,759,761],{"class":314,"line":742},21,[312,744,745],{"class":325},"      throw",[312,747,748],{"class":329}," new",[312,750,345],{"class":488},[312,752,492],{"class":501},[312,754,755],{"class":329},"'",[312,757,758],{"class":365},"Invalid credentials payload",[312,760,755],{"class":329},[312,762,566],{"class":501},[312,764,766],{"class":314,"line":765},22,[312,767,768],{"class":329},"    }\n",[312,770,772,774,776,779,781,784,786,788,790,792],{"class":314,"line":771},23,[312,773,655],{"class":474},[312,775,330],{"class":329},[312,777,778],{"class":333}," email",[312,780,337],{"class":329},[312,782,783],{"class":333}," password",[312,785,356],{"class":329},[312,787,616],{"class":329},[312,789,698],{"class":333},[312,791,291],{"class":329},[312,793,794],{"class":333},"data\n",[312,796,798],{"class":314,"line":797},24,[312,799,468],{"emptyLinePlaceholder":467},[312,801,803,805,808,810,812,815,818,820,823,825,827],{"class":314,"line":802},25,[312,804,655],{"class":474},[312,806,807],{"class":333}," found",[312,809,616],{"class":329},[312,811,663],{"class":325},[312,813,814],{"class":329}," this.",[312,816,817],{"class":333},"usersService",[312,819,291],{"class":329},[312,821,822],{"class":488},"findByEmailForAuth",[312,824,492],{"class":501},[312,826,520],{"class":333},[312,828,566],{"class":501},[312,830,832,834,836,838,841,843,846,848,850,852,854,857,859],{"class":314,"line":831},26,[312,833,721],{"class":325},[312,835,724],{"class":501},[312,837,727],{"class":329},[312,839,840],{"class":333},"found",[312,842,272],{"class":501},[312,844,845],{"class":325},"throw",[312,847,748],{"class":329},[312,849,345],{"class":488},[312,851,492],{"class":501},[312,853,755],{"class":329},[312,855,856],{"class":365},"Invalid credentials",[312,858,755],{"class":329},[312,860,566],{"class":501},[312,862,864],{"class":314,"line":863},27,[312,865,468],{"emptyLinePlaceholder":467},[312,867,869,871,874,876,878,880,882,884,886,889,891,893],{"class":314,"line":868},28,[312,870,655],{"class":474},[312,872,873],{"class":333}," ok",[312,875,616],{"class":329},[312,877,663],{"class":325},[312,879,408],{"class":488},[312,881,492],{"class":501},[312,883,840],{"class":333},[312,885,291],{"class":329},[312,887,888],{"class":333},"passwordHash",[312,890,337],{"class":329},[312,892,783],{"class":333},[312,894,566],{"class":501},[312,896,898,900,902,904,907,909,911,913,915,917,919,921,923],{"class":314,"line":897},29,[312,899,721],{"class":325},[312,901,724],{"class":501},[312,903,727],{"class":329},[312,905,906],{"class":333},"ok",[312,908,272],{"class":501},[312,910,845],{"class":325},[312,912,748],{"class":329},[312,914,345],{"class":488},[312,916,492],{"class":501},[312,918,755],{"class":329},[312,920,856],{"class":365},[312,922,755],{"class":329},[312,924,566],{"class":501},[312,926,928],{"class":314,"line":927},30,[312,929,468],{"emptyLinePlaceholder":467},[312,931,933,935,937,940,942,945,947,950,953,955,957],{"class":314,"line":932},31,[312,934,655],{"class":474},[312,936,330],{"class":329},[312,938,939],{"class":501}," passwordHash",[312,941,505],{"class":329},[312,943,944],{"class":333}," _",[312,946,337],{"class":329},[312,948,949],{"class":329}," ...",[312,951,952],{"class":333},"user",[312,954,356],{"class":329},[312,956,616],{"class":329},[312,958,959],{"class":333}," found\n",[312,961,963,966],{"class":314,"line":962},32,[312,964,965],{"class":325},"    return",[312,967,968],{"class":333}," user\n",[312,970,972],{"class":314,"line":971},33,[312,973,974],{"class":329},"  }\n",[312,976,978],{"class":314,"line":977},34,[312,979,980],{"class":329},"}\n",[982,983,984],"blockquote",{},[187,985,986,992],{},[224,987,988,989,991],{},"Why ",[191,990,856],{}," for both missing-user and wrong-password?"," Don't leak which one was wrong - that's a user enumeration vector. Same error, every time.",[982,994,995],{},[187,996,997,1003,1004,1006],{},[224,998,999,1000,1002],{},"Strip ",[191,1001,888],{}," before returning."," Whatever the provider returns lands on ",[191,1005,201],{},", which downstream handlers may serialize back to the client. Destructure the hash out (or build a dedicated public shape) - never let it escape the provider.",[182,1008,1010],{"id":1009},"authservice","AuthService",[187,1012,1013,1014,1017],{},"Token issuance does ",[224,1015,1016],{},"not"," belong in the controller. Push it into a service so every route that needs a token calls the same method.",[303,1019,1021],{"className":305,"code":1020,"language":307,"meta":308,"style":308},"\u002F\u002F src\u002Fauth\u002Fauth.service.ts\nimport { Injectable, inject } from '@miiajs\u002Fcore'\nimport { JwtService } from '@miiajs\u002Fjwt'\n\n@Injectable()\nexport class AuthService {\n  private jwtService = inject(JwtService)\n\n  async issueTokenFor(user: { id: number; email: string }) {\n    return this.jwtService.sign({ sub: user.id, email: user.email })\n  }\n}\n",[191,1022,1023,1028,1050,1070,1074,1082,1093,1107,1111,1149,1197,1201],{"__ignoreMap":308},[312,1024,1025],{"class":314,"line":315},[312,1026,1027],{"class":318},"\u002F\u002F src\u002Fauth\u002Fauth.service.ts\n",[312,1029,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048],{"class":314,"line":322},[312,1031,326],{"class":325},[312,1033,330],{"class":329},[312,1035,334],{"class":333},[312,1037,337],{"class":329},[312,1039,340],{"class":333},[312,1041,356],{"class":329},[312,1043,359],{"class":325},[312,1045,362],{"class":329},[312,1047,366],{"class":365},[312,1049,369],{"class":329},[312,1051,1052,1054,1056,1059,1061,1063,1065,1068],{"class":314,"line":372},[312,1053,326],{"class":325},[312,1055,330],{"class":329},[312,1057,1058],{"class":333}," JwtService",[312,1060,356],{"class":329},[312,1062,359],{"class":325},[312,1064,362],{"class":329},[312,1066,1067],{"class":365},"@miiajs\u002Fjwt",[312,1069,369],{"class":329},[312,1071,1072],{"class":314,"line":395},[312,1073,468],{"emptyLinePlaceholder":467},[312,1075,1076,1078,1080],{"class":314,"line":422},[312,1077,577],{"class":329},[312,1079,580],{"class":488},[312,1081,583],{"class":333},[312,1083,1084,1086,1088,1091],{"class":314,"line":443},[312,1085,589],{"class":325},[312,1087,592],{"class":474},[312,1089,1090],{"class":595}," AuthService",[312,1092,604],{"class":329},[312,1094,1095,1097,1100,1102,1104],{"class":314,"line":464},[312,1096,610],{"class":474},[312,1098,1099],{"class":501}," jwtService",[312,1101,616],{"class":329},[312,1103,340],{"class":501},[312,1105,1106],{"class":333},"(JwtService)\n",[312,1108,1109],{"class":314,"line":471},[312,1110,468],{"emptyLinePlaceholder":467},[312,1112,1113,1115,1118,1120,1122,1124,1126,1129,1131,1134,1137,1139,1141,1144,1147],{"class":314,"line":498},[312,1114,632],{"class":474},[312,1116,1117],{"class":501}," issueTokenFor",[312,1119,492],{"class":329},[312,1121,952],{"class":640},[312,1123,505],{"class":329},[312,1125,330],{"class":329},[312,1127,1128],{"class":501}," id",[312,1130,505],{"class":329},[312,1132,1133],{"class":595}," number",[312,1135,1136],{"class":329},";",[312,1138,778],{"class":501},[312,1140,505],{"class":329},[312,1142,1143],{"class":595}," string",[312,1145,1146],{"class":329}," })",[312,1148,604],{"class":329},[312,1150,1151,1153,1155,1158,1160,1163,1165,1168,1171,1173,1176,1178,1181,1183,1185,1187,1189,1191,1193,1195],{"class":314,"line":528},[312,1152,965],{"class":325},[312,1154,814],{"class":329},[312,1156,1157],{"class":333},"jwtService",[312,1159,291],{"class":329},[312,1161,1162],{"class":488},"sign",[312,1164,492],{"class":501},[312,1166,1167],{"class":329},"{",[312,1169,1170],{"class":501}," sub",[312,1172,505],{"class":329},[312,1174,1175],{"class":333}," user",[312,1177,291],{"class":329},[312,1179,1180],{"class":333},"id",[312,1182,337],{"class":329},[312,1184,778],{"class":501},[312,1186,505],{"class":329},[312,1188,1175],{"class":333},[312,1190,291],{"class":329},[312,1192,520],{"class":333},[312,1194,356],{"class":329},[312,1196,566],{"class":501},[312,1198,1199],{"class":314,"line":560},[312,1200,974],{"class":329},[312,1202,1203],{"class":314,"line":569},[312,1204,980],{"class":329},[187,1206,1207,1208,1211],{},"In a real app this service also owns ",[191,1209,1210],{},"register()",", password reset, refresh token rotation, and so on - the controller stays thin.",[182,1213,1215],{"id":1214},"login-controller","Login controller",[187,1217,1218,1219,1221,1222,1224],{},"The provider has already placed the user on ",[191,1220,201],{},". The handler only asks ",[191,1223,1010],{}," to mint a token.",[303,1226,1228],{"className":305,"code":1227,"language":307,"meta":308,"style":308},"\u002F\u002F src\u002Fauth\u002Fauth.controller.ts\nimport { Controller, Post, Status, UseGuard, ValidateBody, inject, type RequestContext } from '@miiajs\u002Fcore'\nimport { AuthGuard } from '@miiajs\u002Fauth'\nimport { AuthService } from '.\u002Fauth.service.js'\nimport { LocalAuth } from '.\u002Fproviders\u002Flocal-auth.provider.js'\nimport { RegisterSchema, type RegisterInput } from '.\u002Fschemas\u002Fregister.schema.js'\nimport type { User } from '..\u002Fusers\u002Fusers.schema.js'\n\n@Controller('\u002Fauth')\nexport class AuthController {\n  private authService = inject(AuthService)\n\n  @Post('\u002Flogin')\n  @UseGuard(AuthGuard(LocalAuth))\n  async login(ctx: RequestContext) {\n    const accessToken = await this.authService.issueTokenFor(ctx.user as User)\n    return { accessToken }\n  }\n\n  @Post('\u002Fregister')\n  @Status(201)\n  @ValidateBody(RegisterSchema)\n  async register(ctx: RequestContext) {\n    const data = await ctx.json\u003CRegisterInput>()\n    return this.authService.register(data) \u002F\u002F hash password, persist, issue token - inside the service\n  }\n}\n",[191,1229,1230,1235,1284,1303,1322,1341,1368,1390,1394,1412,1423,1437,1441,1460,1474,1493,1528,1539,1543,1547,1564,1578,1588,1607,1635,1658,1662],{"__ignoreMap":308},[312,1231,1232],{"class":314,"line":315},[312,1233,1234],{"class":318},"\u002F\u002F src\u002Fauth\u002Fauth.controller.ts\n",[312,1236,1237,1239,1241,1244,1246,1249,1251,1254,1256,1259,1261,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":314,"line":322},[312,1238,326],{"class":325},[312,1240,330],{"class":329},[312,1242,1243],{"class":333}," Controller",[312,1245,337],{"class":329},[312,1247,1248],{"class":333}," Post",[312,1250,337],{"class":329},[312,1252,1253],{"class":333}," Status",[312,1255,337],{"class":329},[312,1257,1258],{"class":333}," UseGuard",[312,1260,337],{"class":329},[312,1262,1263],{"class":333}," ValidateBody",[312,1265,337],{"class":329},[312,1267,340],{"class":333},[312,1269,337],{"class":329},[312,1271,350],{"class":325},[312,1273,353],{"class":333},[312,1275,356],{"class":329},[312,1277,359],{"class":325},[312,1279,362],{"class":329},[312,1281,366],{"class":365},[312,1283,369],{"class":329},[312,1285,1286,1288,1290,1293,1295,1297,1299,1301],{"class":314,"line":372},[312,1287,326],{"class":325},[312,1289,330],{"class":329},[312,1291,1292],{"class":333}," AuthGuard",[312,1294,356],{"class":329},[312,1296,359],{"class":325},[312,1298,362],{"class":329},[312,1300,390],{"class":365},[312,1302,369],{"class":329},[312,1304,1305,1307,1309,1311,1313,1315,1317,1320],{"class":314,"line":395},[312,1306,326],{"class":325},[312,1308,330],{"class":329},[312,1310,1090],{"class":333},[312,1312,356],{"class":329},[312,1314,359],{"class":325},[312,1316,362],{"class":329},[312,1318,1319],{"class":365},".\u002Fauth.service.js",[312,1321,369],{"class":329},[312,1323,1324,1326,1328,1330,1332,1334,1336,1339],{"class":314,"line":422},[312,1325,326],{"class":325},[312,1327,330],{"class":329},[312,1329,596],{"class":333},[312,1331,356],{"class":329},[312,1333,359],{"class":325},[312,1335,362],{"class":329},[312,1337,1338],{"class":365},".\u002Fproviders\u002Flocal-auth.provider.js",[312,1340,369],{"class":329},[312,1342,1343,1345,1347,1350,1352,1354,1357,1359,1361,1363,1366],{"class":314,"line":443},[312,1344,326],{"class":325},[312,1346,330],{"class":329},[312,1348,1349],{"class":333}," RegisterSchema",[312,1351,337],{"class":329},[312,1353,350],{"class":325},[312,1355,1356],{"class":333}," RegisterInput",[312,1358,356],{"class":329},[312,1360,359],{"class":325},[312,1362,362],{"class":329},[312,1364,1365],{"class":365},".\u002Fschemas\u002Fregister.schema.js",[312,1367,369],{"class":329},[312,1369,1370,1372,1374,1376,1379,1381,1383,1385,1388],{"class":314,"line":464},[312,1371,326],{"class":325},[312,1373,350],{"class":325},[312,1375,330],{"class":329},[312,1377,1378],{"class":333}," User",[312,1380,356],{"class":329},[312,1382,359],{"class":325},[312,1384,362],{"class":329},[312,1386,1387],{"class":365},"..\u002Fusers\u002Fusers.schema.js",[312,1389,369],{"class":329},[312,1391,1392],{"class":314,"line":471},[312,1393,468],{"emptyLinePlaceholder":467},[312,1395,1396,1398,1401,1403,1405,1408,1410],{"class":314,"line":498},[312,1397,577],{"class":329},[312,1399,1400],{"class":488},"Controller",[312,1402,492],{"class":333},[312,1404,755],{"class":329},[312,1406,1407],{"class":365},"\u002Fauth",[312,1409,755],{"class":329},[312,1411,566],{"class":333},[312,1413,1414,1416,1418,1421],{"class":314,"line":528},[312,1415,589],{"class":325},[312,1417,592],{"class":474},[312,1419,1420],{"class":595}," AuthController",[312,1422,604],{"class":329},[312,1424,1425,1427,1430,1432,1434],{"class":314,"line":560},[312,1426,610],{"class":474},[312,1428,1429],{"class":501}," authService",[312,1431,616],{"class":329},[312,1433,340],{"class":501},[312,1435,1436],{"class":333},"(AuthService)\n",[312,1438,1439],{"class":314,"line":569},[312,1440,468],{"emptyLinePlaceholder":467},[312,1442,1443,1446,1449,1451,1453,1456,1458],{"class":314,"line":574},[312,1444,1445],{"class":329},"  @",[312,1447,1448],{"class":488},"Post",[312,1450,492],{"class":333},[312,1452,755],{"class":329},[312,1454,1455],{"class":365},"\u002Flogin",[312,1457,755],{"class":329},[312,1459,566],{"class":333},[312,1461,1462,1464,1467,1469,1471],{"class":314,"line":586},[312,1463,1445],{"class":329},[312,1465,1466],{"class":488},"UseGuard",[312,1468,492],{"class":333},[312,1470,271],{"class":488},[312,1472,1473],{"class":333},"(LocalAuth))\n",[312,1475,1476,1478,1481,1483,1485,1487,1489,1491],{"class":314,"line":607},[312,1477,632],{"class":474},[312,1479,1480],{"class":501}," login",[312,1482,492],{"class":329},[312,1484,641],{"class":640},[312,1486,505],{"class":329},[312,1488,353],{"class":595},[312,1490,555],{"class":329},[312,1492,604],{"class":329},[312,1494,1495,1497,1500,1502,1504,1506,1509,1511,1514,1516,1518,1520,1522,1524,1526],{"class":314,"line":624},[312,1496,655],{"class":474},[312,1498,1499],{"class":333}," accessToken",[312,1501,616],{"class":329},[312,1503,663],{"class":325},[312,1505,814],{"class":329},[312,1507,1508],{"class":333},"authService",[312,1510,291],{"class":329},[312,1512,1513],{"class":488},"issueTokenFor",[312,1515,492],{"class":501},[312,1517,641],{"class":333},[312,1519,291],{"class":329},[312,1521,952],{"class":333},[312,1523,405],{"class":325},[312,1525,1378],{"class":595},[312,1527,566],{"class":501},[312,1529,1530,1532,1534,1536],{"class":314,"line":629},[312,1531,965],{"class":325},[312,1533,330],{"class":329},[312,1535,1499],{"class":333},[312,1537,1538],{"class":329}," }\n",[312,1540,1541],{"class":314,"line":652},[312,1542,974],{"class":329},[312,1544,1545],{"class":314,"line":693},[312,1546,468],{"emptyLinePlaceholder":467},[312,1548,1549,1551,1553,1555,1557,1560,1562],{"class":314,"line":718},[312,1550,1445],{"class":329},[312,1552,1448],{"class":488},[312,1554,492],{"class":333},[312,1556,755],{"class":329},[312,1558,1559],{"class":365},"\u002Fregister",[312,1561,755],{"class":329},[312,1563,566],{"class":333},[312,1565,1566,1568,1571,1573,1576],{"class":314,"line":742},[312,1567,1445],{"class":329},[312,1569,1570],{"class":488},"Status",[312,1572,492],{"class":333},[312,1574,1575],{"class":551},"201",[312,1577,566],{"class":333},[312,1579,1580,1582,1585],{"class":314,"line":765},[312,1581,1445],{"class":329},[312,1583,1584],{"class":488},"ValidateBody",[312,1586,1587],{"class":333},"(RegisterSchema)\n",[312,1589,1590,1592,1595,1597,1599,1601,1603,1605],{"class":314,"line":771},[312,1591,632],{"class":474},[312,1593,1594],{"class":501}," register",[312,1596,492],{"class":329},[312,1598,641],{"class":640},[312,1600,505],{"class":329},[312,1602,353],{"class":595},[312,1604,555],{"class":329},[312,1606,604],{"class":329},[312,1608,1609,1611,1614,1616,1618,1620,1622,1624,1627,1630,1633],{"class":314,"line":797},[312,1610,655],{"class":474},[312,1612,1613],{"class":333}," data",[312,1615,616],{"class":329},[312,1617,663],{"class":325},[312,1619,666],{"class":333},[312,1621,291],{"class":329},[312,1623,671],{"class":488},[312,1625,1626],{"class":329},"\u003C",[312,1628,1629],{"class":595},"RegisterInput",[312,1631,1632],{"class":329},">",[312,1634,583],{"class":501},[312,1636,1637,1639,1641,1643,1645,1648,1650,1653,1655],{"class":314,"line":802},[312,1638,965],{"class":325},[312,1640,814],{"class":329},[312,1642,1508],{"class":333},[312,1644,291],{"class":329},[312,1646,1647],{"class":488},"register",[312,1649,492],{"class":501},[312,1651,1652],{"class":333},"data",[312,1654,272],{"class":501},[312,1656,1657],{"class":318},"\u002F\u002F hash password, persist, issue token - inside the service\n",[312,1659,1660],{"class":314,"line":831},[312,1661,974],{"class":329},[312,1663,1664],{"class":314,"line":863},[312,1665,980],{"class":329},[187,1667,1668,1669,1671,1672,1675,1676,1678,1679,291],{},"Registration is ",[224,1670,1016],{}," a ",[191,1673,1674],{},"LocalAuth"," use case - there's no existing user to authenticate. It's a normal route with ",[191,1677,261],{},", delegating to ",[191,1680,1681],{},"AuthService.register()",[187,1683,1684,1685,1688,1689,1691,1692,1694,1695,1688,1697,1699],{},"The contrast between the two handlers is the key pipeline detail: ",[191,1686,1687],{},"login"," uses ",[191,1690,230],{},", so the provider consumes ",[191,1693,286],{}," before the handler runs (that's why the provider validates the body itself). ",[191,1696,1647],{},[191,1698,261],{}," instead, which runs as part of the handler wrapping - no guard, no early body consumption.",[182,1701,1703],{"id":1702},"module-wiring","Module wiring",[303,1705,1707],{"className":305,"code":1706,"language":307,"meta":308,"style":308},"\u002F\u002F src\u002Fauth\u002Fauth.module.ts\nimport { Module } from '@miiajs\u002Fcore'\nimport { AuthController } from '.\u002Fauth.controller.js'\nimport { AuthService } from '.\u002Fauth.service.js'\nimport { LocalAuth } from '.\u002Fproviders\u002Flocal-auth.provider.js'\n\n@Module({\n  controllers: [AuthController],\n  providers: [AuthService, LocalAuth],\n})\nexport class AuthModule {}\n",[191,1708,1709,1714,1733,1752,1770,1788,1792,1803,1815,1832,1838],{"__ignoreMap":308},[312,1710,1711],{"class":314,"line":315},[312,1712,1713],{"class":318},"\u002F\u002F src\u002Fauth\u002Fauth.module.ts\n",[312,1715,1716,1718,1720,1723,1725,1727,1729,1731],{"class":314,"line":322},[312,1717,326],{"class":325},[312,1719,330],{"class":329},[312,1721,1722],{"class":333}," Module",[312,1724,356],{"class":329},[312,1726,359],{"class":325},[312,1728,362],{"class":329},[312,1730,366],{"class":365},[312,1732,369],{"class":329},[312,1734,1735,1737,1739,1741,1743,1745,1747,1750],{"class":314,"line":372},[312,1736,326],{"class":325},[312,1738,330],{"class":329},[312,1740,1420],{"class":333},[312,1742,356],{"class":329},[312,1744,359],{"class":325},[312,1746,362],{"class":329},[312,1748,1749],{"class":365},".\u002Fauth.controller.js",[312,1751,369],{"class":329},[312,1753,1754,1756,1758,1760,1762,1764,1766,1768],{"class":314,"line":395},[312,1755,326],{"class":325},[312,1757,330],{"class":329},[312,1759,1090],{"class":333},[312,1761,356],{"class":329},[312,1763,359],{"class":325},[312,1765,362],{"class":329},[312,1767,1319],{"class":365},[312,1769,369],{"class":329},[312,1771,1772,1774,1776,1778,1780,1782,1784,1786],{"class":314,"line":422},[312,1773,326],{"class":325},[312,1775,330],{"class":329},[312,1777,596],{"class":333},[312,1779,356],{"class":329},[312,1781,359],{"class":325},[312,1783,362],{"class":329},[312,1785,1338],{"class":365},[312,1787,369],{"class":329},[312,1789,1790],{"class":314,"line":443},[312,1791,468],{"emptyLinePlaceholder":467},[312,1793,1794,1796,1799,1801],{"class":314,"line":464},[312,1795,577],{"class":329},[312,1797,1798],{"class":488},"Module",[312,1800,492],{"class":333},[312,1802,495],{"class":329},[312,1804,1805,1808,1810,1813],{"class":314,"line":471},[312,1806,1807],{"class":501},"  controllers",[312,1809,505],{"class":329},[312,1811,1812],{"class":333}," [AuthController]",[312,1814,525],{"class":329},[312,1816,1817,1820,1822,1825,1827,1830],{"class":314,"line":498},[312,1818,1819],{"class":501},"  providers",[312,1821,505],{"class":329},[312,1823,1824],{"class":333}," [AuthService",[312,1826,337],{"class":329},[312,1828,1829],{"class":333}," LocalAuth]",[312,1831,525],{"class":329},[312,1833,1834,1836],{"class":314,"line":528},[312,1835,563],{"class":329},[312,1837,566],{"class":333},[312,1839,1840,1842,1844,1847],{"class":314,"line":560},[312,1841,589],{"class":325},[312,1843,592],{"class":474},[312,1845,1846],{"class":595}," AuthModule",[312,1848,1849],{"class":329}," {}\n",[182,1851,1853],{"id":1852},"usersservice-note","UsersService note",[187,1855,1856,1857,1860,1861,1864,1865,1868],{},"The provider calls ",[191,1858,1859],{},"users.findByEmailForAuth(email)"," - a dedicated method that ",[224,1862,1863],{},"returns the password hash",". Keep a separate ",[191,1866,1867],{},"findById"," (or similar) for public reads that excludes the hash. Don't merge them, and never return the auth variant from an HTTP handler.",[182,1870,1872],{"id":1871},"security-notes","Security notes",[1874,1875,1877],"h3",{"id":1876},"password-hashing-argon2id","Password hashing: argon2id",[187,1879,1880,1881,1883,1884,1887],{},"argon2id is the modern recommendation from OWASP. It's resistant to GPU attacks and has tunable memory cost. ",[191,1882,417],{}," is fast (Rust-based) and works on Node, Bun, and Deno. If you have an existing bcrypt deployment, ",[191,1885,1886],{},"bcrypt.compare"," works the same way - swap the import.",[1874,1889,1891],{"id":1890},"dont-leak-which-half-of-the-credentials-is-wrong","Don't leak which half of the credentials is wrong",[187,1893,1894,1895,1899],{},"Both \"user not found\" and \"wrong password\" must return the same error (see the blockquote under ",[1896,1897,301],"a",{"href":1898},"#solution","). Differentiating them turns your login endpoint into a user-enumeration oracle.",[1874,1901,1903],{"id":1902},"never-return-the-password-hash","Never return the password hash",[187,1905,1906,1907,1909,1910,1912,1913,1915,1916,1919,1920,1923],{},"Whatever the provider returns lands on ",[191,1908,201],{},", which downstream handlers may serialize back to the client (see the blockquote under ",[1896,1911,301],{"href":1898},"). Either destructure the hash out at the provider boundary, or keep a separate ",[191,1914,822],{}," \u002F public-shape split at the ",[191,1917,1918],{},"UsersService"," level - which is exactly what the ",[1896,1921,1853],{"href":1922},"#usersservice-note"," above describes.",[182,1925,1927],{"id":1926},"see-also","See also",[1929,1930,1931,1943,1952,1957],"ul",{},[221,1932,1933,1937,1938,1940,1941],{},[1896,1934,1935],{"href":114},[191,1936,390],{}," - ",[191,1939,209],{}," interface, ",[191,1942,271],{},[221,1944,1945,1937,1949],{},[1896,1946,1947],{"href":132},[191,1948,1067],{},[191,1950,1951],{},"JwtService.sign()",[221,1953,1954,1956],{},[1896,1955,119],{"href":120}," - verify the token issued here on subsequent requests",[221,1958,1959,1961],{},[1896,1960,127],{"href":128}," - third-party login",[1963,1964,1965],"style",{},"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 .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 .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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html .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);}",{"title":308,"searchDepth":322,"depth":322,"links":1967},[1968,1969,1971,1973,1974,1975,1976,1977,1978,1983],{"id":184,"depth":322,"text":185},{"id":205,"depth":322,"text":1970},"Why an AuthProvider and not a plain controller?",{"id":257,"depth":322,"text":1972},"Pipeline note: provider runs before @ValidateBody",{"id":300,"depth":322,"text":301},{"id":1009,"depth":322,"text":1010},{"id":1214,"depth":322,"text":1215},{"id":1702,"depth":322,"text":1703},{"id":1852,"depth":322,"text":1853},{"id":1871,"depth":322,"text":1872,"children":1979},[1980,1981,1982],{"id":1876,"depth":372,"text":1877},{"id":1890,"depth":372,"text":1891},{"id":1902,"depth":372,"text":1903},{"id":1926,"depth":322,"text":1927},"Username\u002Fpassword login as an AuthProvider - argon2 password verification, Zod body validation inside the provider.","md",{},{"title":123,"description":1984},"bg3E1rbPbGIX5jKse3kP8k8SiiaaBXTtNA2xf4W2zeQ",[1990,1992],{"title":119,"path":120,"stem":121,"description":1991,"children":-1},"A 15-line Injectable class that authenticates Bearer tokens with JwtService and loads the user.",{"title":127,"path":128,"stem":129,"description":1993,"children":-1},"GitHub login as both a server-side redirect flow and an AuthProvider that verifies external access tokens.",1778575278672]