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