[{"data":1,"prerenderedAt":1556},["ShallowReactive",2],{"navigation":3,"\u002Fguides\u002Fregistration":203,"\u002Fguides\u002Fregistration-surround":1551},[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":41,"body":205,"description":1544,"extension":1545,"links":1546,"meta":1547,"navigation":1548,"path":42,"seo":1549,"stem":43,"__hash__":1550},"docs\u002F2.guides\u002F02.registration.md",{"type":206,"value":207,"toc":1533},"minimark",[208,221,242,247,389,407,411,422,438,452,456,474,615,620,630,643,647,662,665,708,715,903,909,913,920,1355,1358,1377,1388,1403,1414,1432,1446,1450,1453,1500,1504,1529],[209,210,211,212,216,217,220],"p",{},"AuthEndpoints creates accounts in three ways. Every account needs an email address. When ",[213,214,215],"code",{},"RequireConfirmedAccount"," is ",[213,218,219],{},"true"," (the default), password and passkey registration do not sign the user in. GitHub and Google registration signs the user in when the provider returns a verified email.",[209,222,223,224,216,227,230,231,216,234,237,238,241],{},"The examples use the cookie facade defaults: ",[213,225,226],{},"IdentityPath",[213,228,229],{},"\u002Fidentity",", ",[213,232,233],{},"PasskeyPath",[213,235,236],{},"\u002Faccount",", and the host maps external sign-in under ",[213,239,240],{},"\u002Fauth\u002Fexternal",".",[243,244,246],"h2",{"id":245},"compare-the-registration-methods","Compare the registration methods",[248,249,250,278],"table",{},[251,252,253],"thead",{},[254,255,256,260,263,266,269,272,275],"tr",{},[257,258,259],"th",{},"Method",[257,261,262],{},"Package",[257,264,265],{},"Endpoints",[257,267,268],{},"Sends a confirmation email",[257,270,271],{},"CSRF",[257,273,274],{},"Signs in on success",[257,276,277],{},"Two-factor",[279,280,281,316,351],"tbody",{},[254,282,283,291,296,305,308,311,313],{},[284,285,286],"td",{},[287,288,290],"a",{"href":289},"#register-with-an-email-and-password","Email and password",[284,292,293],{},[213,294,295],{},"AuthEndpoints",[284,297,298,301,302],{},[213,299,300],{},"POST \u002Fidentity\u002Fregister",", then ",[213,303,304],{},"GET \u002Fidentity\u002FconfirmEmail",[284,306,307],{},"Yes",[284,309,310],{},"No",[284,312,310],{},[284,314,315],{},"The user turns on 2FA later.",[254,317,318,324,329,337,339,341,348],{},[284,319,320],{},[287,321,323],{"href":322},"#register-with-a-passkey","Passkey",[284,325,326,328],{},[213,327,295],{}," (passkeys are on by default)",[284,330,331,301,334],{},[213,332,333],{},"POST \u002Faccount\u002Fpasskeys\u002Fregister\u002Foptions",[213,335,336],{},"POST \u002Faccount\u002Fpasskeys\u002Fregister",[284,338,307],{},[284,340,307],{},[284,342,343,344,347],{},"Only when ",[213,345,346],{},"CanSignInAsync"," passes. By default it does not.",[284,349,350],{},"Passkey sign-in skips 2FA.",[254,352,353,359,372,378,380,383,386],{},[284,354,355],{},[287,356,358],{"href":357},"#register-with-github-or-google","GitHub or Google",[284,360,361,364,365,368,369],{},[213,362,363],{},"AuthEndpoints.External.OAuth"," and ",[213,366,367],{},"AuthEndpoints.OAuth.GitHub"," or ",[213,370,371],{},"AuthEndpoints.OAuth.Google",[284,373,374,377],{},[213,375,376],{},"GET \u002Fauth\u002Fexternal\u002Flogin\u002F{provider}",", then the callback",[284,379,310],{},[284,381,382],{},"No. The OAuth state cookie protects the callback.",[284,384,385],{},"Yes, when the provider email is verified",[284,387,388],{},"OAuth sign-in skips 2FA.",[209,390,391,392,395,396,399,400,403,404,241],{},"A duplicate email never tells the caller that the address is taken. Password registration returns ",[213,393,394],{},"200",". Passkey registration returns the generic ",[213,397,398],{},"400"," \"Unable to complete registration.\" GitHub and Google refuse the sign-in with ",[213,401,402],{},"auto_link_disabled"," unless you turn on ",[213,405,406],{},"AutoLinkByEmail",[243,408,410],{"id":409},"get-a-csrf-token","Get a CSRF token",[209,412,413,414,417,418,421],{},"Passkey ceremonies require an antiforgery token. Password registration does not. On the cookie facade, get the token from ",[213,415,416],{},"GET \u002Fidentity\u002FcsrfToken"," and send it in the ",[213,419,420],{},"RequestVerificationToken"," header:",[423,424,429],"pre",{"className":425,"code":426,"language":427,"meta":428,"style":428},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const { csrfToken } = await fetch('\u002Fidentity\u002FcsrfToken', { credentials: 'include' }).then(r => r.json());\n","js","",[213,430,431],{"__ignoreMap":428},[432,433,436],"span",{"class":434,"line":435},"line",1,[432,437,426],{},[209,439,440,441,443,444,447,448,451],{},"A missing or wrong token returns ",[213,442,398],{}," with the plain-text body ",[213,445,446],{},"Invalid or missing CSRF token."," The ",[287,449,163],{"href":450},"\u002Fmodules\u002Fcsrf\u002F"," page lists every route that needs a token.",[243,453,455],{"id":454},"register-with-an-email-and-password","Register with an email and password",[209,457,458,459,462,463,466,467,470,471,241],{},"Before you start, register a real ",[213,460,461],{},"IEmailSender\u003CTUser>",". In Production, startup fails while Identity's no-op sender is registered, unless you set ",[213,464,465],{},"RequireEmailSenderInProduction"," to ",[213,468,469],{},"false",". Set password rules with ",[213,472,473],{},"ConfigureIdentity",[475,476,477,521,557,568,609],"ol",{},[478,479,480,481,483,484,241,487],"li",{},"Send the email and password to ",[213,482,300],{},". The body is Identity's ",[213,485,486],{},"RegisterRequest",[423,488,490],{"className":425,"code":489,"language":427,"meta":428,"style":428},"const response = await fetch('\u002Fidentity\u002Fregister', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application\u002Fjson' },\n  body: JSON.stringify({ email, password })\n});\n",[213,491,492,497,503,509,515],{"__ignoreMap":428},[432,493,494],{"class":434,"line":435},[432,495,496],{},"const response = await fetch('\u002Fidentity\u002Fregister', {\n",[432,498,500],{"class":434,"line":499},2,[432,501,502],{},"  method: 'POST',\n",[432,504,506],{"class":434,"line":505},3,[432,507,508],{},"  headers: { 'Content-Type': 'application\u002Fjson' },\n",[432,510,512],{"class":434,"line":511},4,[432,513,514],{},"  body: JSON.stringify({ email, password })\n",[432,516,518],{"class":434,"line":517},5,[432,519,520],{},"});\n",[478,522,523,524],{},"Check the response:",[525,526,527,535,547],"ul",{},[478,528,529,531,532,534],{},[213,530,394],{}," with an empty body: show a \"Check your email\" screen. The user is not signed in. A duplicate email also returns ",[213,533,394],{},", so use the same copy.",[478,536,537,539,540,543,544,241],{},[213,538,398],{}," validation problem: show the errors. Examples are ",[213,541,542],{},"InvalidEmail"," and the password-rule codes such as ",[213,545,546],{},"PasswordTooShort",[478,548,549,552,553,556],{},[213,550,551],{},"429",": the ",[213,554,555],{},"AuthEndpoints.AccountAbuse"," rate limit (10 requests per minute per IP) rejected the request.",[478,558,559,560,563,564,567],{},"AuthEndpoints calls ",[213,561,562],{},"IEmailSender\u003CTUser>.SendConfirmationLinkAsync"," with a link to ",[213,565,566],{},"GET \u002Fidentity\u002FconfirmEmail?userId=...&code=...",". The user name is set to the email.",[478,569,570,571,574,575],{},"The user opens the link. The response depends on ",[213,572,573],{},"EmailConfirmation.ConfirmEmailRedirectUri",":",[525,576,577,587],{},[478,578,579,580,582,583,586],{},"Not set: ",[213,581,394],{}," with the text \"Thank you for confirming your email.\", or ",[213,584,585],{},"401"," when the link is not valid.",[478,588,589,590,593,594,368,597,600,601,604,605,241],{},"Set: ",[213,591,592],{},"302"," to that URI with ",[213,595,596],{},"status=confirmed",[213,598,599],{},"status=failed",", and ",[213,602,603],{},"flow=confirm",". See ",[287,606,608],{"href":607},"\u002Fmodules\u002Fconfiguration\u002F#email-confirmation","Email confirmation",[478,610,611,612,241],{},"Sign the user in. See ",[287,613,46],{"href":614},"\u002Fguides\u002Fsign-in\u002F",[616,617,619],"h3",{"id":618},"resend-the-confirmation-email","Resend the confirmation email",[209,621,622,625,626,629],{},[213,623,624],{},"POST \u002Fidentity\u002FresendConfirmationEmail"," sends the link again to the signed-in user's email. It requires a signed-in user and a CSRF token, and it ignores the ",[213,627,628],{},"email"," field in the body.",[209,631,632,633,216,635,637,638,216,640,642],{},"When ",[213,634,215],{},[213,636,219],{},", an unconfirmed user cannot sign in. That user cannot call this endpoint. Resend helps only when ",[213,639,215],{},[213,641,469],{},", or after a signed-in user changes their email. AuthEndpoints has no anonymous resend endpoint.",[243,644,646],{"id":645},"register-with-a-passkey","Register with a passkey",[648,649,652],"callout",{"color":650,"icon":651},"warning","i-lucide-triangle-alert",[209,653,654,655,658,659,241],{},"Use AuthEndpoints 3.1.1 or later. In 3.1.0, when JWT is not turned on, passkey registration and passkey sign-in return ",[213,656,657],{},"500",". So does any other CSRF-protected endpoint called without a session. To upgrade, run ",[213,660,661],{},"dotnet add package AuthEndpoints --version 3.1.1",[209,663,664],{},"A passkey account has no password. Before you start:",[525,666,667,674,694],{},[478,668,669,670,673],{},"Set ",[213,671,672],{},"Passkeys.ServerDomain",". It is required in Production.",[478,675,676,677,680,681,368,684,687,688,368,691,241],{},"Use a ",[213,678,679],{},"TUser"," with a ",[213,682,683],{},"string",[213,685,686],{},"Guid"," key, such as ",[213,689,690],{},"IdentityUser",[213,692,693],{},"IdentityUser\u003CGuid>",[478,695,696,697,700,701,704,705,241],{},"To choose the minted user id, register an ",[213,698,699],{},"IPasskeyUserIdFactory"," with ",[213,702,703],{},"AddPasskeyUserIdFactory",". The default id is ",[213,706,707],{},"Guid.NewGuid()",[209,709,710,711,714],{},"Send ",[213,712,713],{},"credentials: 'include'"," on both requests. The ceremony state lives in a cookie.",[475,716,717,720,735,742,839,890,896],{},[478,718,719],{},"Collect the email address.",[478,721,722,723,700,725,728,729,731,732,241],{},"Get creation options from ",[213,724,333],{},[213,726,727],{},"{ \"email\" }"," and the CSRF header. The endpoint returns WebAuthn creation options even when the email is taken. An invalid email returns a ",[213,730,398],{}," validation problem with the key ",[213,733,734],{},"Email",[478,736,737,738,741],{},"Call ",[213,739,740],{},"navigator.credentials.create",". If the user cancels, stop. Do not call the register endpoint.",[478,743,744,745,700,747,750,751,368,754,757,758],{},"Send the credential to ",[213,746,336],{},[213,748,749],{},"{ \"email\", \"credentialJson\" }"," and the CSRF header. Add ",[213,752,753],{},"?useCookies=true",[213,755,756],{},"?useSessionCookies=true"," so that a successful sign-in sets a cookie instead of returning bearer tokens.",[423,759,761],{"className":425,"code":760,"language":427,"meta":428,"style":428},"const headers = { 'Content-Type': 'application\u002Fjson', 'RequestVerificationToken': csrfToken };\n\nconst options = await fetch('\u002Faccount\u002Fpasskeys\u002Fregister\u002Foptions', {\n  method: 'POST', credentials: 'include', headers, body: JSON.stringify({ email })\n}).then(r => r.json());\n\nconst credential = await navigator.credentials.create({\n  publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options)\n});\n\nconst response = await fetch('\u002Faccount\u002Fpasskeys\u002Fregister?useCookies=true', {\n  method: 'POST', credentials: 'include', headers,\n  body: JSON.stringify({ email, credentialJson: JSON.stringify(credential) })\n});\n",[213,762,763,768,774,779,784,789,794,800,806,811,816,822,828,834],{"__ignoreMap":428},[432,764,765],{"class":434,"line":435},[432,766,767],{},"const headers = { 'Content-Type': 'application\u002Fjson', 'RequestVerificationToken': csrfToken };\n",[432,769,770],{"class":434,"line":499},[432,771,773],{"emptyLinePlaceholder":772},true,"\n",[432,775,776],{"class":434,"line":505},[432,777,778],{},"const options = await fetch('\u002Faccount\u002Fpasskeys\u002Fregister\u002Foptions', {\n",[432,780,781],{"class":434,"line":511},[432,782,783],{},"  method: 'POST', credentials: 'include', headers, body: JSON.stringify({ email })\n",[432,785,786],{"class":434,"line":517},[432,787,788],{},"}).then(r => r.json());\n",[432,790,792],{"class":434,"line":791},6,[432,793,773],{"emptyLinePlaceholder":772},[432,795,797],{"class":434,"line":796},7,[432,798,799],{},"const credential = await navigator.credentials.create({\n",[432,801,803],{"class":434,"line":802},8,[432,804,805],{},"  publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options)\n",[432,807,809],{"class":434,"line":808},9,[432,810,520],{},[432,812,814],{"class":434,"line":813},10,[432,815,773],{"emptyLinePlaceholder":772},[432,817,819],{"class":434,"line":818},11,[432,820,821],{},"const response = await fetch('\u002Faccount\u002Fpasskeys\u002Fregister?useCookies=true', {\n",[432,823,825],{"class":434,"line":824},12,[432,826,827],{},"  method: 'POST', credentials: 'include', headers,\n",[432,829,831],{"class":434,"line":830},13,[432,832,833],{},"  body: JSON.stringify({ email, credentialJson: JSON.stringify(credential) })\n",[432,835,837],{"class":434,"line":836},14,[432,838,520],{},[478,840,523,841],{},[525,842,843,849,867,872,881],{},[478,844,845,848],{},[213,846,847],{},"200 { \"credentialId\": \"...\" }"," with no cookie: the account exists but cannot sign in yet, because the email is not confirmed. Show the same \"Check your email\" screen as password registration.",[478,850,851,853,854,216,856,858,859,862,863,866],{},[213,852,394],{}," with a cookie: sign-in was allowed, for example because ",[213,855,215],{},[213,857,469],{},". With ",[213,860,861],{},"JwtPasskeySignInCompleter",", the body is ",[213,864,865],{},"{ \"accessToken\", \"tokenType\" }"," and the response sets the JWT refresh cookie.",[478,868,869,871],{},[213,870,398],{}," \"Unable to complete registration.\": the email is taken, the attestation failed, or the user id already exists. Show generic copy.",[478,873,874,876,877,880],{},[213,875,398],{}," \"The browser did not provide a passkey.\": ",[213,878,879],{},"credentialJson"," was empty.",[478,882,883,885,886,889],{},[213,884,398],{}," validation problem ",[213,887,888],{},"InvalidPasskeyState",": no ceremony was underway. Start again from step 2.",[478,891,892,893,895],{},"AuthEndpoints sends the same confirmation email as password registration. The user confirms with the same ",[213,894,304],{}," link.",[478,897,898,899,241],{},"After confirmation, the user signs in with the passkey. See ",[287,900,902],{"href":901},"\u002Fguides\u002Fsign-in\u002F#sign-in-with-a-passkey","Sign in with a passkey",[209,904,905,906,241],{},"Passwordless registration never adds a passkey to an existing account. To add a passkey to a signed-in account, see ",[287,907,71],{"href":908},"\u002Fguides\u002Fmanage-passkeys\u002F",[243,910,912],{"id":911},"register-with-github-or-google","Register with GitHub or Google",[209,914,915,916,919],{},"External OAuth ships in separate preview packages. ",[213,917,918],{},"MapAuthEndpoints"," does not map it.",[475,921,922,964,1242,1318,1329,1340],{},[478,923,924,925],{},"Install the provider packages:",[423,926,930],{"className":927,"code":928,"language":929,"meta":428,"style":428},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","dotnet add package AuthEndpoints.OAuth.GitHub --prerelease\ndotnet add package AuthEndpoints.OAuth.Google --prerelease\n","bash",[213,931,932,951],{"__ignoreMap":428},[432,933,934,938,942,945,948],{"class":434,"line":435},[432,935,937],{"class":936},"sBMFI","dotnet",[432,939,941],{"class":940},"sfazB"," add",[432,943,944],{"class":940}," package",[432,946,947],{"class":940}," AuthEndpoints.OAuth.GitHub",[432,949,950],{"class":940}," --prerelease\n",[432,952,953,955,957,959,962],{"class":434,"line":499},[432,954,937],{"class":936},[432,956,941],{"class":940},[432,958,944],{"class":940},[432,960,961],{"class":940}," AuthEndpoints.OAuth.Google",[432,963,950],{"class":940},[478,965,966,967,970,971,974,975,364,978,981,982,364,985,988,989],{},"Register the services. ",[213,968,969],{},"AddExternalAuthEndpoints\u003CTUser>()"," returns an ",[213,972,973],{},"ExternalAuthBuilder",". ",[213,976,977],{},"AddGitHub",[213,979,980],{},"AddGoogle"," validate ",[213,983,984],{},"ClientId",[213,986,987],{},"ClientSecret"," at startup.",[423,990,994],{"className":991,"code":992,"language":993,"meta":428,"style":428},"language-cs shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","builder.Services.AddExternalAuthEndpoints\u003CAppUser>(o =>\n{\n    o.RequireVerifiedEmail = true; \u002F\u002F default\n    o.DefaultReturnUrl = \"\u002F\";\n})\n.AddGitHub(o =>\n{\n    o.ClientId = builder.Configuration[\"Authentication:GitHub:ClientId\"]!;\n    o.ClientSecret = builder.Configuration[\"Authentication:GitHub:ClientSecret\"]!;\n})\n.AddGoogle(o =>\n{\n    o.ClientId = builder.Configuration[\"Authentication:Google:ClientId\"]!;\n    o.ClientSecret = builder.Configuration[\"Authentication:Google:ClientSecret\"]!;\n});\n","cs",[213,995,996,1029,1034,1058,1081,1086,1099,1103,1135,1163,1167,1179,1183,1210,1237],{"__ignoreMap":428},[432,997,998,1002,1005,1008,1010,1014,1017,1020,1023,1026],{"class":434,"line":435},[432,999,1001],{"class":1000},"sTEyZ","builder",[432,1003,241],{"class":1004},"sMK4o",[432,1006,1007],{"class":1000},"Services",[432,1009,241],{"class":1004},[432,1011,1013],{"class":1012},"s2Zo4","AddExternalAuthEndpoints",[432,1015,1016],{"class":1004},"\u003C",[432,1018,1019],{"class":936},"AppUser",[432,1021,1022],{"class":1004},">(",[432,1024,1025],{"class":936},"o",[432,1027,1028],{"class":1004}," =>\n",[432,1030,1031],{"class":434,"line":499},[432,1032,1033],{"class":1004},"{\n",[432,1035,1036,1039,1041,1044,1047,1051,1054],{"class":434,"line":505},[432,1037,1038],{"class":1000},"    o",[432,1040,241],{"class":1004},[432,1042,1043],{"class":1000},"RequireVerifiedEmail ",[432,1045,1046],{"class":1004},"=",[432,1048,1050],{"class":1049},"sfNiH"," true",[432,1052,1053],{"class":1004},";",[432,1055,1057],{"class":1056},"sHwdD"," \u002F\u002F default\n",[432,1059,1060,1062,1064,1067,1069,1072,1075,1078],{"class":434,"line":511},[432,1061,1038],{"class":1000},[432,1063,241],{"class":1004},[432,1065,1066],{"class":1000},"DefaultReturnUrl ",[432,1068,1046],{"class":1004},[432,1070,1071],{"class":1004}," \"",[432,1073,1074],{"class":940},"\u002F",[432,1076,1077],{"class":1004},"\"",[432,1079,1080],{"class":1004},";\n",[432,1082,1083],{"class":434,"line":517},[432,1084,1085],{"class":1004},"})\n",[432,1087,1088,1090,1092,1095,1097],{"class":434,"line":791},[432,1089,241],{"class":1004},[432,1091,977],{"class":1012},[432,1093,1094],{"class":1004},"(",[432,1096,1025],{"class":936},[432,1098,1028],{"class":1004},[432,1100,1101],{"class":434,"line":796},[432,1102,1033],{"class":1004},[432,1104,1105,1107,1109,1112,1114,1117,1119,1122,1125,1127,1130,1132],{"class":434,"line":802},[432,1106,1038],{"class":1000},[432,1108,241],{"class":1004},[432,1110,1111],{"class":1000},"ClientId ",[432,1113,1046],{"class":1004},[432,1115,1116],{"class":1000}," builder",[432,1118,241],{"class":1004},[432,1120,1121],{"class":1000},"Configuration",[432,1123,1124],{"class":1004},"[",[432,1126,1077],{"class":1004},[432,1128,1129],{"class":940},"Authentication:GitHub:ClientId",[432,1131,1077],{"class":1004},[432,1133,1134],{"class":1004},"]!;\n",[432,1136,1137,1139,1141,1144,1146,1148,1150,1152,1154,1156,1159,1161],{"class":434,"line":808},[432,1138,1038],{"class":1000},[432,1140,241],{"class":1004},[432,1142,1143],{"class":1000},"ClientSecret ",[432,1145,1046],{"class":1004},[432,1147,1116],{"class":1000},[432,1149,241],{"class":1004},[432,1151,1121],{"class":1000},[432,1153,1124],{"class":1004},[432,1155,1077],{"class":1004},[432,1157,1158],{"class":940},"Authentication:GitHub:ClientSecret",[432,1160,1077],{"class":1004},[432,1162,1134],{"class":1004},[432,1164,1165],{"class":434,"line":813},[432,1166,1085],{"class":1004},[432,1168,1169,1171,1173,1175,1177],{"class":434,"line":818},[432,1170,241],{"class":1004},[432,1172,980],{"class":1012},[432,1174,1094],{"class":1004},[432,1176,1025],{"class":936},[432,1178,1028],{"class":1004},[432,1180,1181],{"class":434,"line":824},[432,1182,1033],{"class":1004},[432,1184,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203,1206,1208],{"class":434,"line":830},[432,1186,1038],{"class":1000},[432,1188,241],{"class":1004},[432,1190,1111],{"class":1000},[432,1192,1046],{"class":1004},[432,1194,1116],{"class":1000},[432,1196,241],{"class":1004},[432,1198,1121],{"class":1000},[432,1200,1124],{"class":1004},[432,1202,1077],{"class":1004},[432,1204,1205],{"class":940},"Authentication:Google:ClientId",[432,1207,1077],{"class":1004},[432,1209,1134],{"class":1004},[432,1211,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1233,1235],{"class":434,"line":836},[432,1213,1038],{"class":1000},[432,1215,241],{"class":1004},[432,1217,1143],{"class":1000},[432,1219,1046],{"class":1004},[432,1221,1116],{"class":1000},[432,1223,241],{"class":1004},[432,1225,1121],{"class":1000},[432,1227,1124],{"class":1004},[432,1229,1077],{"class":1004},[432,1231,1232],{"class":940},"Authentication:Google:ClientSecret",[432,1234,1077],{"class":1004},[432,1236,1134],{"class":1004},[432,1238,1240],{"class":434,"line":1239},15,[432,1241,520],{"class":1004},[478,1243,1244,1245],{},"Map the routes:",[423,1246,1248],{"className":991,"code":1247,"language":993,"meta":428,"style":428},"var external = app.MapGroup(\"\u002Fauth\u002Fexternal\");\nexternal.MapGitHubAuthEndpoints\u003CAppUser>(); \u002F\u002F GET login\u002Fgithub, login\u002Fgithub\u002Fcallback\nexternal.MapGoogleAuthEndpoints\u003CAppUser>(); \u002F\u002F GET login\u002Fgoogle, login\u002Fgoogle\u002Fcallback\n",[213,1249,1250,1280,1300],{"__ignoreMap":428},[432,1251,1252,1255,1258,1261,1264,1266,1269,1271,1273,1275,1277],{"class":434,"line":435},[432,1253,1254],{"class":936},"var",[432,1256,1257],{"class":936}," external",[432,1259,1260],{"class":1004}," =",[432,1262,1263],{"class":1000}," app",[432,1265,241],{"class":1004},[432,1267,1268],{"class":1012},"MapGroup",[432,1270,1094],{"class":1004},[432,1272,1077],{"class":1004},[432,1274,240],{"class":940},[432,1276,1077],{"class":1004},[432,1278,1279],{"class":1004},");\n",[432,1281,1282,1285,1287,1290,1292,1294,1297],{"class":434,"line":499},[432,1283,1284],{"class":1000},"external",[432,1286,241],{"class":1004},[432,1288,1289],{"class":1012},"MapGitHubAuthEndpoints",[432,1291,1016],{"class":1004},[432,1293,1019],{"class":936},[432,1295,1296],{"class":1004},">();",[432,1298,1299],{"class":1056}," \u002F\u002F GET login\u002Fgithub, login\u002Fgithub\u002Fcallback\n",[432,1301,1302,1304,1306,1309,1311,1313,1315],{"class":434,"line":505},[432,1303,1284],{"class":1000},[432,1305,241],{"class":1004},[432,1307,1308],{"class":1012},"MapGoogleAuthEndpoints",[432,1310,1016],{"class":1004},[432,1312,1019],{"class":936},[432,1314,1296],{"class":1004},[432,1316,1317],{"class":1056}," \u002F\u002F GET login\u002Fgoogle, login\u002Fgoogle\u002Fcallback\n",[478,1319,1320,1321,1324,1325,1328],{},"Register the handler callback paths with each identity provider: ",[213,1322,1323],{},"\u002Fsignin-github"," for GitHub and ",[213,1326,1327],{},"\u002Fsignin-google"," for Google.",[478,1330,1331,1332,1335,1336,1339],{},"Host an error page at ",[213,1333,1334],{},"ErrorPath"," (default ",[213,1337,1338],{},"\u002Fauth\u002Fexternal\u002Ferror",").",[478,1341,1342,1343,574,1346],{},"Start the flow with a top-level navigation, not ",[213,1344,1345],{},"fetch",[423,1347,1349],{"className":425,"code":1348,"language":427,"meta":428,"style":428},"window.location.assign('\u002Fauth\u002Fexternal\u002Flogin\u002Fgithub?returnUrl=' + encodeURIComponent('\u002Fdashboard'));\n",[213,1350,1351],{"__ignoreMap":428},[432,1352,1353],{"class":434,"line":435},[432,1354,1348],{},[209,1356,1357],{},"The callback creates an account when all of these are true:",[525,1359,1360,1363,1366,1369],{},[478,1361,1362],{},"No local user has this provider login yet.",[478,1364,1365],{},"The provider returned an email.",[478,1367,1368],{},"No local user has that email.",[478,1370,1371,1372,216,1375,241],{},"The provider marked the email as verified, or ",[213,1373,1374],{},"RequireVerifiedEmail",[213,1376,469],{},[209,1378,1379,1380,1383,1384,1387],{},"The new user has no password. ",[213,1381,1382],{},"EmailConfirmed"," matches the provider's verified flag. AuthEndpoints links the login, signs the user in, and redirects to ",[213,1385,1386],{},"returnUrl",". It does not send a confirmation email.",[209,1389,1390,1391,216,1393,1395,1396,1399,1400,1402],{},"If ",[213,1392,1374],{},[213,1394,469],{}," and the email is not verified, the callback still creates the account. It then refuses the sign-in with ",[213,1397,1398],{},"user_not_allowed",", because ",[213,1401,215],{}," applies.",[209,1404,1405,1406,1409,1410,1413],{},"GitHub reads verified addresses from ",[213,1407,1408],{},"GET https:\u002F\u002Fapi.github.com\u002Fuser\u002Femails",". Google reads ",[213,1411,1412],{},"email_verified"," from the userinfo response.",[209,1415,1416,1417,1420,1421,1424,1425,1428,1429,241],{},"When the callback refuses, it redirects to ",[213,1418,1419],{},"ErrorPath?error=...&error_description=...",". A client that prefers ",[213,1422,1423],{},"application\u002Fjson"," over ",[213,1426,1427],{},"text\u002Fhtml"," gets a problem details response instead. The error codes are listed in ",[287,1430,167],{"href":1431},"\u002Fmodules\u002Ferrors\u002F#external-oauth-errors",[209,1433,1434,1435,700,1438,1441,1442,1445],{},"To get a JWT instead of a cookie, register ",[213,1436,1437],{},"JwtExternalLoginCompleter\u003CTUser>",[213,1439,1440],{},"AddCompleter",". After the redirect, the client calls ",[213,1443,1444],{},"POST \u002Fauth\u002Frefresh"," with a CSRF token to get an access token.",[243,1447,1449],{"id":1448},"what-registration-does-not-support","What registration does not support",[209,1451,1452],{},"AuthEndpoints does not include these features:",[525,1454,1455,1458,1461,1464,1467,1477,1486],{},[478,1456,1457],{},"Sign-up with a magic link or an emailed code.",[478,1459,1460],{},"Sign-up with a phone number or SMS.",[478,1462,1463],{},"Sign-up with a user name and no email.",[478,1465,1466],{},"An anonymous endpoint that resends the confirmation email.",[478,1468,1469,1470,364,1473,1476],{},"GitHub or Google sign-up that returns Identity bearer tokens. Only ",[213,1471,1472],{},"CookieExternalLoginCompleter",[213,1474,1475],{},"JwtExternalLoginCompleter"," exist.",[478,1478,1479,1480,364,1483,241],{},"Built-in Apple or Microsoft providers. You can add a custom provider with ",[213,1481,1482],{},"IExternalAuthProvider",[213,1484,1485],{},"AddProvider",[478,1487,1488,1489,1492,1493,1496,1497,241],{},"Setting a first password on a passkey-only or OAuth-only account through ",[213,1490,1491],{},"POST \u002Fidentity\u002Fmanage\u002Finfo",". That endpoint requires ",[213,1494,1495],{},"oldPassword"," when you send ",[213,1498,1499],{},"newPassword",[243,1501,1503],{"id":1502},"related","Related",[525,1505,1506,1510,1514,1519,1524],{},[478,1507,1508],{},[287,1509,46],{"href":614},[478,1511,1512],{},[287,1513,158],{"href":607},[478,1515,1516],{},[287,1517,145],{"href":1518},"\u002Fmodules\u002Fpasskeys\u002F",[478,1520,1521],{},[287,1522,154],{"href":1523},"\u002Fmodules\u002Fexternal-oauth\u002F",[478,1525,1526],{},[287,1527,194],{"href":1528},"\u002Fconcepts\u002Fsecurity-model\u002F",[1530,1531,1532],"style",{},"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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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}",{"title":428,"searchDepth":435,"depth":499,"links":1534},[1535,1536,1537,1540,1541,1542,1543],{"id":245,"depth":499,"text":246},{"id":409,"depth":499,"text":410},{"id":454,"depth":499,"text":455,"children":1538},[1539],{"id":618,"depth":505,"text":619},{"id":645,"depth":499,"text":646},{"id":911,"depth":499,"text":912},{"id":1448,"depth":499,"text":1449},{"id":1502,"depth":499,"text":1503},"Create accounts with an email and password, a passkey, or a GitHub or Google account.","md",null,{},{"icon":44},{"title":41,"description":1544},"2FwYgGyNfbCkv9-3rFmXxACIoPCOlolZX_V4rtdD1iM",[1552,1554],{"title":34,"path":35,"stem":36,"description":1553,"icon":39,"children":-1},"Task-based steps for registration, sign-in, account management, and production setup.",{"title":46,"path":47,"stem":48,"description":1555,"icon":49,"children":-1},"Sign users in with a password, a passkey, or GitHub or Google, and handle two-factor codes and recovery codes.",1791123626075]