[{"data":1,"prerenderedAt":453},["ShallowReactive",2],{"navigation":3,"\u002Fgetting-started\u002Fchoose-a-sign-in-stack":203,"\u002Fgetting-started\u002Fchoose-a-sign-in-stack-surround":448},[4,33,96,114,177],{"title":5,"path":6,"stem":7,"children":8,"icon":32},"Get started","\u002Fgetting-started","1.getting-started\u002F1.index",[9,12,17,22,27],{"title":10,"path":6,"stem":7,"icon":11},"Introduction","i-lucide-house",{"title":13,"path":14,"stem":15,"icon":16},"Install AuthEndpoints","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":18,"path":19,"stem":20,"icon":21},"Quick start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",{"title":23,"path":24,"stem":25,"icon":26},"Choose a sign-in stack","\u002Fgetting-started\u002Fchoose-a-sign-in-stack","1.getting-started\u002F4.choose-a-sign-in-stack","i-lucide-signpost",{"title":28,"path":29,"stem":30,"icon":31},"Use the AuthEndpoints skill with coding agents","\u002Fgetting-started\u002Fai-agents","1.getting-started\u002F5.ai-agents","i-lucide-bot","i-lucide-rocket",{"title":34,"path":35,"stem":36,"children":37,"icon":95},"Guides","\u002Fguides","2.guides\u002F01.index",[38,40,45,50,55,60,65,70,75,80,85,90],{"title":34,"path":35,"stem":36,"icon":39},"i-lucide-list-checks",{"title":41,"path":42,"stem":43,"icon":44},"Register users","\u002Fguides\u002Fregistration","2.guides\u002F02.registration","i-lucide-user-plus",{"title":46,"path":47,"stem":48,"icon":49},"Sign users in","\u002Fguides\u002Fsign-in","2.guides\u002F03.sign-in","i-lucide-log-in",{"title":51,"path":52,"stem":53,"icon":54},"Sign users out","\u002Fguides\u002Fsign-out","2.guides\u002F04.sign-out","i-lucide-log-out",{"title":56,"path":57,"stem":58,"icon":59},"Turn on two-factor authentication","\u002Fguides\u002Ftwo-factor","2.guides\u002F05.two-factor","i-lucide-smartphone",{"title":61,"path":62,"stem":63,"icon":64},"Reset a forgotten password","\u002Fguides\u002Freset-password","2.guides\u002F06.reset-password","i-lucide-key-round",{"title":66,"path":67,"stem":68,"icon":69},"Change a user's email or password","\u002Fguides\u002Fmanage-account","2.guides\u002F07.manage-account","i-lucide-user-cog",{"title":71,"path":72,"stem":73,"icon":74},"Add, rename, and remove passkeys","\u002Fguides\u002Fmanage-passkeys","2.guides\u002F08.manage-passkeys","i-lucide-scan-face",{"title":76,"path":77,"stem":78,"icon":79},"Link and unlink GitHub or Google accounts","\u002Fguides\u002Flink-external-accounts","2.guides\u002F09.link-external-accounts","i-lucide-link",{"title":81,"path":82,"stem":83,"icon":84},"Require step-up before sensitive actions","\u002Fguides\u002Fstep-up","2.guides\u002F10.step-up","i-lucide-shield-check",{"title":86,"path":87,"stem":88,"icon":89},"Call the API from a browser","\u002Fguides\u002Fbrowser-clients","2.guides\u002F11.browser-clients","i-lucide-globe",{"title":91,"path":92,"stem":93,"icon":94},"Prepare for production","\u002Fguides\u002Fproduction","2.guides\u002F12.production","i-lucide-factory","i-lucide-waypoints",{"title":97,"path":98,"stem":99,"children":100,"icon":113},"Composable endpoints","\u002Fcomposables","3.composables\u002F1.index",[101,104,108],{"title":102,"path":98,"stem":99,"icon":103},"How composition works","i-lucide-layout-grid",{"title":105,"path":106,"stem":107,"icon":39},"Composition requirements","\u002Fcomposables\u002Frequirements","3.composables\u002F2.requirements",{"title":109,"path":110,"stem":111,"icon":112},"Compose a custom auth stack","\u002Fcomposables\u002Frecipes","3.composables\u002F3.recipes","i-lucide-book-marked","i-lucide-blocks",{"title":115,"icon":116,"path":117,"stem":118,"children":119,"page":176},"Reference","i-lucide-book-open","\u002Fmodules","4.modules",[120,125,129,134,139,144,148,153,157,162,166,171],{"title":121,"path":122,"stem":123,"icon":124},"Endpoint reference","\u002Fmodules\u002Fendpoints","4.modules\u002F01.endpoints","i-lucide-route",{"title":126,"path":127,"stem":128,"icon":69},"Identity management module","\u002Fmodules\u002Fidentity-management","4.modules\u002F02.identity-management",{"title":130,"path":131,"stem":132,"icon":133},"Cookie sign-in module","\u002Fmodules\u002Fcookie-auth","4.modules\u002F03.cookie-auth","i-lucide-cookie",{"title":135,"path":136,"stem":137,"icon":138},"Identity bearer sign-in module","\u002Fmodules\u002Fbearer-auth","4.modules\u002F04.bearer-auth","i-lucide-key",{"title":140,"path":141,"stem":142,"icon":143},"JWT module","\u002Fmodules\u002Fjwt","4.modules\u002F05.jwt","i-lucide-fingerprint",{"title":145,"path":146,"stem":147,"icon":74},"Passkeys module","\u002Fmodules\u002Fpasskeys","4.modules\u002F06.passkeys",{"title":149,"path":150,"stem":151,"icon":152},"ReAuth module","\u002Fmodules\u002Freauth","4.modules\u002F07.reauth","i-lucide-shield-alert",{"title":154,"path":155,"stem":156,"icon":49},"External OAuth packages","\u002Fmodules\u002Fexternal-oauth","4.modules\u002F08.external-oauth",{"title":158,"path":159,"stem":160,"icon":161},"Configuration options","\u002Fmodules\u002Fconfiguration","4.modules\u002F09.configuration","i-lucide-settings",{"title":163,"path":164,"stem":165,"icon":84},"Antiforgery (CSRF) rules","\u002Fmodules\u002Fcsrf","4.modules\u002F10.csrf",{"title":167,"path":168,"stem":169,"icon":170},"Responses and errors","\u002Fmodules\u002Ferrors","4.modules\u002F11.errors","i-lucide-circle-alert",{"title":172,"path":173,"stem":174,"icon":175},"Rate-limit policies","\u002Fmodules\u002Frate-limits","4.modules\u002F12.rate-limits","i-lucide-gauge",false,{"title":178,"icon":179,"path":180,"stem":181,"children":182,"page":176},"Concepts","i-lucide-lightbulb","\u002Fconcepts","5.concepts",[183,188,193,198],{"title":184,"path":185,"stem":186,"icon":187},"AuthEndpoints compared with other options","\u002Fconcepts\u002Fcompare","5.concepts\u002F1.compare","i-lucide-git-compare",{"title":189,"path":190,"stem":191,"icon":192},"Stock Identity endpoints vs AuthEndpoints","\u002Fconcepts\u002Fstock-identity-vs-authendpoints","5.concepts\u002F2.stock-identity-vs-authendpoints","i-lucide-columns-2",{"title":194,"path":195,"stem":196,"icon":197},"Security model","\u002Fconcepts\u002Fsecurity-model","5.concepts\u002F3.security-model","i-lucide-shield",{"title":199,"path":200,"stem":201,"icon":202},"FAQ","\u002Fconcepts\u002Ffaq","5.concepts\u002F4.faq","i-lucide-circle-help",{"id":204,"title":23,"body":205,"description":441,"extension":442,"links":443,"meta":444,"navigation":445,"path":24,"seo":446,"stem":25,"__hash__":447},"docs\u002F1.getting-started\u002F4.choose-a-sign-in-stack.md",{"type":206,"value":207,"toc":430},"minimark",[208,212,217,294,298,301,308,312,322,333,337,344,357,361,364,390,396,411,415],[209,210,211],"p",{},"AuthEndpoints has three password sign-in stacks: cookie, Identity bearer, and JWT. Passkeys and GitHub or Google sign-in sit beside them and finish into one of those stacks. The right stack depends on where the client keeps its credentials.",[213,214,216],"h2",{"id":215},"the-short-answer","The short answer",[218,219,220,236],"table",{},[221,222,223],"thead",{},[224,225,226,230,233],"tr",{},[227,228,229],"th",{},"Client",[227,231,232],{},"Stack",[227,234,235],{},"Why",[237,238,239,251,267,278],"tbody",{},[224,240,241,245,248],{},[242,243,244],"td",{},"Browser app on the same site as the API",[242,246,247],{},"Cookie (the facade default)",[242,249,250],{},"The browser stores an HttpOnly cookie. Script cannot read it. CSRF tokens protect the routes that change state.",[224,252,253,256,259],{},[242,254,255],{},"Browser app that needs an access token for other APIs",[242,257,258],{},"JWT",[242,260,261,262,266],{},"The access token lives in memory. The refresh token stays in an HttpOnly ",[263,264,265],"code",{},"SameSite=Strict"," cookie.",[224,268,269,272,275],{},[242,270,271],{},"Native or mobile app",[242,273,274],{},"Identity bearer",[242,276,277],{},"The app stores the access and refresh tokens itself. It needs no cookie jar for sign-in and refresh.",[224,279,280,283,286],{},[242,281,282],{},"Browser and native clients on one API",[242,284,285],{},"Cookie or JWT for the browser, Identity bearer for native",[242,287,288,289,293],{},"Map two sign-in groups. See ",[290,291,109],"a",{"href":292},"\u002Fcomposables\u002Frecipes\u002F",".",[213,295,297],{"id":296},"why-cookies-are-the-default","Why cookies are the default",[209,299,300],{},"Most AuthEndpoints hosts serve a browser app from the same site as the API. For that client, a cookie is the smallest attack surface. JavaScript cannot read an HttpOnly cookie, so a cross-site scripting bug cannot copy the session out of the browser. The cost is CSRF: the browser sends the cookie on every request, so every state-changing route checks an antiforgery token. AuthEndpoints adds those checks for you. Cookies also make passkeys simple, because the WebAuthn ceremony already runs in the browser.",[209,302,303,304,307],{},"The cookie stack ends a session cleanly. ",[263,305,306],{},"POST \u002Fidentity\u002Flogout"," clears the application cookie, the 2FA remember-client cookie, and the ReAuth cookie.",[213,309,311],{"id":310},"when-jwt-is-the-better-fit","When JWT is the better fit",[209,313,314,315,317,318,321],{},"Pick JWT when the browser app must send a bearer token, for example to a second API that validates the same JWT. AuthEndpoints keeps the long-lived part out of script: the refresh token is HttpOnly, ",[263,316,265],{},", stored hashed, and rotated on each use. Reuse of an old refresh token revokes the whole token family. ",[263,319,320],{},"POST \u002Fauth\u002Flogout"," revokes the family too.",[209,323,324,325,328,329,332],{},"JWT asks more of the host. You add a migration for the refresh-token table, and you configure the signing key, issuer, and audience. The client keeps the access token in memory and calls ",[263,326,327],{},"POST \u002Fauth\u002Frefresh"," with a CSRF token when it expires. JWT is not a facade sign-in mode. Turn it on with ",[263,330,331],{},"Jwt.Enabled"," beside the cookie stack, or compose it on its own.",[213,334,336],{"id":335},"why-native-apps-use-identity-bearer","Why native apps use Identity bearer",[209,338,339,340,343],{},"A native app has no browser cookie jar to lean on and no cross-site request risk. Identity bearer returns both tokens in the response body, and ",[263,341,342],{},"POST \u002Fidentity\u002Frefresh"," takes the refresh token in the body. That fits secure on-device storage.",[209,345,346,347,349,350,353,354,293],{},"Know two trade-offs. First, ",[263,348,306],{}," does not revoke Identity bearer tokens. They stay valid until they expire. Second, the bearer facade maps no ",[263,351,352],{},"\u002FcsrfToken"," route, but passkey ceremonies still need a CSRF token. To use passkeys on a bearer host, map an antiforgery token endpoint yourself. See ",[290,355,18],{"href":356},"\u002Fgetting-started\u002Fquick-start\u002F#native-and-mobile",[213,358,360],{"id":359},"where-passkeys-and-github-or-google-fit","Where passkeys and GitHub or Google fit",[209,362,363],{},"Passkeys and external OAuth are first-factor methods. They do not replace a stack. Each finishes into one:",[365,366,367,383],"ul",{},[368,369,370,371,374,375,378,379,382],"li",{},"The default passkey completer sets the application cookie when the request has ",[263,372,373],{},"useCookies=true"," or ",[263,376,377],{},"useSessionCookies=true",", and returns Identity bearer tokens otherwise. ",[263,380,381],{},"JwtPasskeySignInCompleter"," returns a JWT instead.",[368,384,385,386,389],{},"The default OAuth completer sets the application cookie. ",[263,387,388],{},"JwtExternalLoginCompleter"," sets the JWT refresh cookie. No completer returns Identity bearer tokens.",[209,391,392,393,293],{},"Both methods skip two-factor authentication. See ",[290,394,194],{"href":395},"\u002Fconcepts\u002Fsecurity-model\u002F",[397,398,401],"callout",{"color":399,"icon":400},"warning","i-lucide-triangle-alert",[209,402,403,404,407,408,293],{},"Use AuthEndpoints 3.1.1 or later. In 3.1.0, when JWT is not turned on, passkey registration and passkey sign-in return ",[263,405,406],{},"500",". So does any other CSRF-protected endpoint called without a session. To upgrade, run ",[263,409,410],{},"dotnet add package AuthEndpoints --version 3.1.1",[213,412,414],{"id":413},"related","Related",[365,416,417,422,426],{},[368,418,419],{},[290,420,46],{"href":421},"\u002Fguides\u002Fsign-in\u002F",[368,423,424],{},[290,425,109],{"href":292},[368,427,428],{},[290,429,194],{"href":395},{"title":431,"searchDepth":432,"depth":433,"links":434},"",1,2,[435,436,437,438,439,440],{"id":215,"depth":433,"text":216},{"id":296,"depth":433,"text":297},{"id":310,"depth":433,"text":311},{"id":335,"depth":433,"text":336},{"id":359,"depth":433,"text":360},{"id":413,"depth":433,"text":414},"Which AuthEndpoints sign-in stack fits a browser app, a native app, or both, and why.","md",null,{},{"icon":26},{"title":23,"description":441},"_E2tN6RVfA_Iw0clWyuRrXtZtdahpzRMUTg5g9Rgs9s",[449,451],{"title":18,"path":19,"stem":20,"description":450,"icon":21,"children":-1},"Build a minimal ASP.NET Core API where a user registers, confirms their email, signs in with a cookie, and signs out.",{"title":28,"path":29,"stem":30,"description":452,"icon":31,"children":-1},"Add the AuthEndpoints skill to your app so that coding agents follow the library's conventions.",1791123625921]