[{"data":1,"prerenderedAt":435},["ShallowReactive",2],{"navigation":3,"\u002Fconcepts\u002Fsecurity-model":203,"\u002Fconcepts\u002Fsecurity-model-surround":430},[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":194,"body":205,"description":423,"extension":424,"links":425,"meta":426,"navigation":427,"path":195,"seo":428,"stem":196,"__hash__":429},"docs\u002F5.concepts\u002F3.security-model.md",{"type":206,"value":207,"toc":409},"minimark",[208,212,217,220,259,270,274,281,285,294,301,305,314,318,321,332,339,343,353,360,371,375,380,384,387,391],[209,210,211],"p",{},"AuthEndpoints adds protection in layers on top of ASP.NET Core Identity. This page explains what each layer covers, and where the library makes a trade-off that you should know about.",[213,214,216],"h2",{"id":215},"responses-do-not-reveal-which-accounts-exist","Responses do not reveal which accounts exist",[209,218,219],{},"An attacker who can tell whether an email is registered can target that account. AuthEndpoints returns the same answer whether or not an account exists:",[221,222,223,232,235,252],"ul",{},[224,225,226,227,231],"li",{},"Password registration returns ",[228,229,230],"code",{},"200"," for a new email and for a duplicate.",[224,233,234],{},"Passkey registration options come back for any valid email. Registration with a taken email fails with the same generic message as a failed attestation.",[224,236,237,240,241,243,244,247,248,251],{},[228,238,239],{},"POST \u002Fidentity\u002FforgotPassword"," always returns ",[228,242,230],{},". ",[228,245,246],{},"POST \u002Fidentity\u002FresetPassword"," returns ",[228,249,250],{},"InvalidToken"," for an unknown email, an unconfirmed email, and a bad code alike.",[224,253,254,255,258],{},"Login returns ",[228,256,257],{},"Invalid credentials."," for a wrong password, a lockout, and an unconfirmed email.",[209,260,261,262,265,266,269],{},"One exception is deliberate. Identifier-first passkey sign-in (",[228,263,264],{},"requestOptions"," with an email) can reveal through ",[228,267,268],{},"allowCredentials"," that an account has passkeys. Omit the email to use discoverable credentials and avoid that.",[213,271,273],{"id":272},"csrf-checks-protect-cookie-sessions","CSRF checks protect cookie sessions",[209,275,276,277,280],{},"A browser sends cookies on every request, including requests that another site starts. AuthEndpoints adds an antiforgery filter to every route that changes state for a signed-in cookie user, and to every passkey ceremony. The filter skips the check when a bearer scheme authenticated the request and no application or external cookie is present, because a cross-site page cannot attach a bearer header. Login, register, forgot password, and reset password have no check. They do not act on an existing session. See ",[278,279,163],"a",{"href":164},".",[213,282,284],{"id":283},"rate-limits-and-lockout-slow-guessing","Rate limits and lockout slow guessing",[209,286,287,288,291,292,280],{},"Login routes share a token bucket per IP. Registration and password-reset routes share a fixed window per IP. Passkey options and passkey registration have tighter limits. Identity lockout is on for every password check (",[228,289,290],{},"lockoutOnFailure: true","), including the password proof in step-up. See ",[278,293,172],{"href":173},[209,295,296,297,300],{},"Identity bearer ",[228,298,299],{},"POST \u002Fidentity\u002Frefresh"," has no rate limit. A refresh token is a long random value, so guessing is not practical, but the route does accept unlimited attempts.",[213,302,304],{"id":303},"step-up-guards-the-changes-that-matter-most","Step-up guards the changes that matter most",[209,306,307,308,311,312,280],{},"A stolen session should not be enough to take over an account. Changing the email, the password, the 2FA settings, or the passkeys requires a fresh ReAuth proof that expires after 5 minutes by default. Unlinking an external login requires one too. The ReAuth cookie is ",[228,309,310],{},"SameSite=Strict"," and does not slide. See ",[278,313,81],{"href":82},[213,315,317],{"id":316},"two-factor-is-a-password-add-on","Two-factor is a password add-on",[209,319,320],{},"AuthEndpoints treats 2FA as a second factor for password sign-in only. Passkey sign-in and GitHub or Google sign-in do not ask for a 2FA code, even when the user turned 2FA on. The passkey completers and the OAuth completers sign the user in directly.",[209,322,323,324,327,328,331],{},"For passkeys, this is a reasonable default. A passkey is phishing-resistant and is usually unlocked with a device biometric or PIN, so it already combines two factors. For OAuth, the reasoning is weaker. The account is as strong as the user's GitHub or Google account, which may or may not use 2FA. If your threat model requires 2FA on every path, register a custom ",[228,325,326],{},"IExternalLoginCompleter\u003CTUser>"," or ",[228,329,330],{},"IPasskeySignInCompleter\u003CTUser>"," that checks it.",[209,333,334,335,338],{},"A related trade-off: a persistent cookie login with a valid authenticator code sets Identity's remember-client cookie. Later password logins from that browser skip 2FA until the user sends ",[228,336,337],{},"forgetMachine"," or logs out. Session logins and recovery-code logins never set it.",[213,340,342],{"id":341},"email-confirmation-gates-sign-in","Email confirmation gates sign-in",[209,344,345,348,349,352],{},[228,346,347],{},"RequireConfirmedAccount"," is ",[228,350,351],{},"true"," by default. Until a user confirms the email, password, passkey, and JWT sign-in all fail. This blocks an attacker from claiming an address they do not own.",[209,354,355,356,359],{},"The cost is that resend needs a session. ",[228,357,358],{},"POST \u002Fidentity\u002FresendConfirmationEmail"," requires a signed-in user, and an unconfirmed user cannot sign in. A user who loses the first email cannot get another one through AuthEndpoints today.",[209,361,362,363,366,367,370],{},"GitHub and Google accounts skip the confirmation email. ",[228,364,365],{},"EmailConfirmed"," comes from the provider's verified flag, and ",[228,368,369],{},"RequireVerifiedEmail"," refuses unverified emails by default.",[213,372,374],{"id":373},"startup-checks-catch-unsafe-production-settings","Startup checks catch unsafe Production settings",[209,376,377,378,280],{},"In Production, startup fails when the host has no real email sender, no passkey domain, or the default JWT issuer or audience. In every environment, startup fails for a missing or short symmetric JWT key and for an empty GitHub or Google client id or secret. See ",[278,379,91],{"href":92},[213,381,383],{"id":382},"what-the-host-owns","What the host owns",[209,385,386],{},"AuthEndpoints does not configure HTTPS, CORS, or the security of the client. Serve the API over HTTPS. Allow credentials in CORS only for the exact origins you trust. Keep Identity bearer tokens in secure device storage.",[213,388,390],{"id":389},"related","Related",[221,392,393,397,401,405],{},[224,394,395],{},[278,396,163],{"href":164},[224,398,399],{},[278,400,172],{"href":173},[224,402,403],{},[278,404,23],{"href":24},[224,406,407],{},[278,408,91],{"href":92},{"title":410,"searchDepth":411,"depth":412,"links":413},"",1,2,[414,415,416,417,418,419,420,421,422],{"id":215,"depth":412,"text":216},{"id":272,"depth":412,"text":273},{"id":283,"depth":412,"text":284},{"id":303,"depth":412,"text":304},{"id":316,"depth":412,"text":317},{"id":341,"depth":412,"text":342},{"id":373,"depth":412,"text":374},{"id":382,"depth":412,"text":383},{"id":389,"depth":412,"text":390},"How AuthEndpoints protects sign-in and account changes, and which trade-offs it leaves to the host.","md",null,{},{"icon":197},{"title":194,"description":423},"oahsxVrLh5e98BbGsauIPN7PaDYcQSgkoy-ZDkwvT7k",[431,433],{"title":189,"path":190,"stem":191,"description":432,"icon":192,"children":-1},"Side-by-side register, login, password reset, and 2FA for MapIdentityApi and the AuthEndpoints facade.",{"title":199,"path":200,"stem":201,"description":434,"icon":202,"children":-1},"Does AuthEndpoints replace Identity? Cookie or JWT? Passkeys, 2FA, composing routes, and Sign in with Google as a provider.",1791096173920]