[{"data":1,"prerenderedAt":1758},["ShallowReactive",2],{"navigation":3,"\u002Fmodules\u002Fexternal-oauth":203,"\u002Fmodules\u002Fexternal-oauth-surround":1753},[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":154,"body":205,"description":1746,"extension":1747,"links":1748,"meta":1749,"navigation":1750,"path":155,"seo":1751,"stem":156,"__hash__":1752},"docs\u002F4.modules\u002F08.external-oauth.md",{"type":206,"value":207,"toc":1730},"minimark",[208,244,264,269,326,338,362,377,381,765,768,778,822,827,839,897,904,908,1008,1011,1044,1048,1053,1215,1219,1251,1255,1430,1434,1545,1548,1575,1587,1590,1613,1659,1665,1669,1699,1703,1726],[209,210,211,212,216,217,221,222,225,226,229,230,233,234,237,238,243],"p",{},"External OAuth (GitHub, Google) ships as ",[213,214,215],"strong",{},"separate NuGet packages",". The core package ",[218,219,220],"code",{},"AuthEndpoints.External.OAuth"," has no GitHub or Google handler dependency. Install ",[218,223,224],{},"AuthEndpoints.OAuth.GitHub"," and\u002For ",[218,227,228],{},"AuthEndpoints.OAuth.Google","; each depends on the core package. It is compose-only (not wired into ",[218,231,232],{},"MapAuthEndpoints","). Default completion issues an Identity application cookie; replace the completer for JWT. Passkeys use the same completer idea via ",[218,235,236],{},"IPasskeySignInCompleter"," in the core package — see ",[239,240,242],"a",{"href":241},"\u002Fmodules\u002Fpasskeys\u002F#sign-in-completer","Passkeys",".",[245,246,249],"callout",{"color":247,"icon":248},"warning","i-lucide-flask-conical",[209,250,251,254,255,258,259,263],{},[213,252,253],{},"Preview."," Independent versioning from the core ",[218,256,257],{},"AuthEndpoints"," package — see the ",[239,260,262],{"href":261},"\u002Fchangelog\u002F","changelog",". GitHub and Google are their own packages at the same preview version.",[265,266,268],"h2",{"id":267},"install","Install",[270,271,276],"pre",{"className":272,"code":273,"language":274,"meta":275,"style":275},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","dotnet add package AuthEndpoints\ndotnet add package AuthEndpoints.OAuth.GitHub --prerelease\ndotnet add package AuthEndpoints.OAuth.Google --prerelease\n","bash","",[218,277,278,297,312],{"__ignoreMap":275},[279,280,283,287,291,294],"span",{"class":281,"line":282},"line",1,[279,284,286],{"class":285},"sBMFI","dotnet",[279,288,290],{"class":289},"sfazB"," add",[279,292,293],{"class":289}," package",[279,295,296],{"class":289}," AuthEndpoints\n",[279,298,300,302,304,306,309],{"class":281,"line":299},2,[279,301,286],{"class":285},[279,303,290],{"class":289},[279,305,293],{"class":289},[279,307,308],{"class":289}," AuthEndpoints.OAuth.GitHub",[279,310,311],{"class":289}," --prerelease\n",[279,313,315,317,319,321,324],{"class":281,"line":314},3,[279,316,286],{"class":285},[279,318,290],{"class":289},[279,320,293],{"class":289},[279,322,323],{"class":289}," AuthEndpoints.OAuth.Google",[279,325,311],{"class":289},[209,327,328],{},[239,329,333],{"href":330,"rel":331},"https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FAuthEndpoints.External.OAuth\u002F",[332],"nofollow",[334,335],"img",{"alt":336,"src":337},"nuget","https:\u002F\u002Fimg.shields.io\u002Fnuget\u002Fvpre\u002FAuthEndpoints.External.OAuth?label=External.OAuth&logo=NuGet&style=flat-square",[209,339,340,341,344,345,348,349,352,353,357,358,361],{},"Use ",[218,342,343],{},"--prerelease"," for the OAuth preview package. Requires ASP.NET Core Identity (",[218,346,347],{},"UserManager"," \u002F ",[218,350,351],{},"SignInManager",") already configured, typically via ",[239,354,356],{"href":355},"\u002Fgetting-started\u002Fquick-start\u002F","AddAuthEndpoints",". It does ",[213,359,360],{},"not"," call Identity management HTTP APIs; those remain optional alongside External.",[209,363,364,365,368,369,372,373,376],{},"Hosts must call ",[218,366,367],{},"UseRateLimiter()"," (included in ",[218,370,371],{},"UseAuthEndpoints","). ",[218,374,375],{},"AddExternalAuthEndpoints"," registers the login rate-limit policy.",[265,378,380],{"id":379},"di","DI",[270,382,386],{"className":383,"code":384,"language":385,"meta":275,"style":275},"language-cs shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","using AuthEndpoints.External.OAuth;\nusing AuthEndpoints.OAuth.GitHub;\nusing AuthEndpoints.OAuth.Google;\n\nbuilder.Services.AddExternalAuthEndpoints\u003CAppUser>(o =>\n{\n    o.RequireVerifiedEmail = true;   \u002F\u002F default\n    o.AutoLinkByEmail = false;       \u002F\u002F default; opt in requires verified provider email and confirmed local email\n    o.DefaultReturnUrl = \"\u002F\";        \u002F\u002F rooted local path\n    o.ErrorPath = \"\u002Fauth\u002Fexternal\u002Ferror\";\n    \u002F\u002F o.AllowedReturnUrlOrigins.Add(\"https:\u002F\u002Fapp.example.com\"); \u002F\u002F optional absolute allowlist\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",[218,387,388,412,429,446,453,484,490,515,535,561,582,591,597,612,617,650,679,684,698,703,731,759],{"__ignoreMap":275},[279,389,390,394,398,401,404,406,409],{"class":281,"line":282},[279,391,393],{"class":392},"sbssI","using",[279,395,397],{"class":396},"sTEyZ"," AuthEndpoints",[279,399,243],{"class":400},"sMK4o",[279,402,403],{"class":396},"External",[279,405,243],{"class":400},[279,407,408],{"class":396},"OAuth",[279,410,411],{"class":400},";\n",[279,413,414,416,418,420,422,424,427],{"class":281,"line":299},[279,415,393],{"class":392},[279,417,397],{"class":396},[279,419,243],{"class":400},[279,421,408],{"class":396},[279,423,243],{"class":400},[279,425,426],{"class":396},"GitHub",[279,428,411],{"class":400},[279,430,431,433,435,437,439,441,444],{"class":281,"line":314},[279,432,393],{"class":392},[279,434,397],{"class":396},[279,436,243],{"class":400},[279,438,408],{"class":396},[279,440,243],{"class":400},[279,442,443],{"class":396},"Google",[279,445,411],{"class":400},[279,447,449],{"class":281,"line":448},4,[279,450,452],{"emptyLinePlaceholder":451},true,"\n",[279,454,456,459,461,464,466,469,472,475,478,481],{"class":281,"line":455},5,[279,457,458],{"class":396},"builder",[279,460,243],{"class":400},[279,462,463],{"class":396},"Services",[279,465,243],{"class":400},[279,467,375],{"class":468},"s2Zo4",[279,470,471],{"class":400},"\u003C",[279,473,474],{"class":285},"AppUser",[279,476,477],{"class":400},">(",[279,479,480],{"class":285},"o",[279,482,483],{"class":400}," =>\n",[279,485,487],{"class":281,"line":486},6,[279,488,489],{"class":400},"{\n",[279,491,493,496,498,501,504,508,511],{"class":281,"line":492},7,[279,494,495],{"class":396},"    o",[279,497,243],{"class":400},[279,499,500],{"class":396},"RequireVerifiedEmail ",[279,502,503],{"class":400},"=",[279,505,507],{"class":506},"sfNiH"," true",[279,509,510],{"class":400},";",[279,512,514],{"class":513},"sHwdD","   \u002F\u002F default\n",[279,516,518,520,522,525,527,530,532],{"class":281,"line":517},8,[279,519,495],{"class":396},[279,521,243],{"class":400},[279,523,524],{"class":396},"AutoLinkByEmail ",[279,526,503],{"class":400},[279,528,529],{"class":506}," false",[279,531,510],{"class":400},[279,533,534],{"class":513},"       \u002F\u002F default; opt in requires verified provider email and confirmed local email\n",[279,536,538,540,542,545,547,550,553,556,558],{"class":281,"line":537},9,[279,539,495],{"class":396},[279,541,243],{"class":400},[279,543,544],{"class":396},"DefaultReturnUrl ",[279,546,503],{"class":400},[279,548,549],{"class":400}," \"",[279,551,552],{"class":289},"\u002F",[279,554,555],{"class":400},"\"",[279,557,510],{"class":400},[279,559,560],{"class":513},"        \u002F\u002F rooted local path\n",[279,562,564,566,568,571,573,575,578,580],{"class":281,"line":563},10,[279,565,495],{"class":396},[279,567,243],{"class":400},[279,569,570],{"class":396},"ErrorPath ",[279,572,503],{"class":400},[279,574,549],{"class":400},[279,576,577],{"class":289},"\u002Fauth\u002Fexternal\u002Ferror",[279,579,555],{"class":400},[279,581,411],{"class":400},[279,583,585,588],{"class":281,"line":584},11,[279,586,587],{"class":513},"    \u002F\u002F o.AllowedReturnUrlOrigins.Add(\"https:\u002F\u002Fapp.example.com\");",[279,589,590],{"class":513}," \u002F\u002F optional absolute allowlist\n",[279,592,594],{"class":281,"line":593},12,[279,595,596],{"class":400},"})\n",[279,598,600,602,605,608,610],{"class":281,"line":599},13,[279,601,243],{"class":400},[279,603,604],{"class":468},"AddGitHub",[279,606,607],{"class":400},"(",[279,609,480],{"class":285},[279,611,483],{"class":400},[279,613,615],{"class":281,"line":614},14,[279,616,489],{"class":400},[279,618,620,622,624,627,629,632,634,637,640,642,645,647],{"class":281,"line":619},15,[279,621,495],{"class":396},[279,623,243],{"class":400},[279,625,626],{"class":396},"ClientId ",[279,628,503],{"class":400},[279,630,631],{"class":396}," builder",[279,633,243],{"class":400},[279,635,636],{"class":396},"Configuration",[279,638,639],{"class":400},"[",[279,641,555],{"class":400},[279,643,644],{"class":289},"Authentication:GitHub:ClientId",[279,646,555],{"class":400},[279,648,649],{"class":400},"]!;\n",[279,651,653,655,657,660,662,664,666,668,670,672,675,677],{"class":281,"line":652},16,[279,654,495],{"class":396},[279,656,243],{"class":400},[279,658,659],{"class":396},"ClientSecret ",[279,661,503],{"class":400},[279,663,631],{"class":396},[279,665,243],{"class":400},[279,667,636],{"class":396},[279,669,639],{"class":400},[279,671,555],{"class":400},[279,673,674],{"class":289},"Authentication:GitHub:ClientSecret",[279,676,555],{"class":400},[279,678,649],{"class":400},[279,680,682],{"class":281,"line":681},17,[279,683,596],{"class":400},[279,685,687,689,692,694,696],{"class":281,"line":686},18,[279,688,243],{"class":400},[279,690,691],{"class":468},"AddGoogle",[279,693,607],{"class":400},[279,695,480],{"class":285},[279,697,483],{"class":400},[279,699,701],{"class":281,"line":700},19,[279,702,489],{"class":400},[279,704,706,708,710,712,714,716,718,720,722,724,727,729],{"class":281,"line":705},20,[279,707,495],{"class":396},[279,709,243],{"class":400},[279,711,626],{"class":396},[279,713,503],{"class":400},[279,715,631],{"class":396},[279,717,243],{"class":400},[279,719,636],{"class":396},[279,721,639],{"class":400},[279,723,555],{"class":400},[279,725,726],{"class":289},"Authentication:Google:ClientId",[279,728,555],{"class":400},[279,730,649],{"class":400},[279,732,734,736,738,740,742,744,746,748,750,752,755,757],{"class":281,"line":733},21,[279,735,495],{"class":396},[279,737,243],{"class":400},[279,739,659],{"class":396},[279,741,503],{"class":400},[279,743,631],{"class":396},[279,745,243],{"class":400},[279,747,636],{"class":396},[279,749,639],{"class":400},[279,751,555],{"class":400},[279,753,754],{"class":289},"Authentication:Google:ClientSecret",[279,756,555],{"class":400},[279,758,649],{"class":400},[279,760,762],{"class":281,"line":761},22,[279,763,764],{"class":400},"});\n",[209,766,767],{},"ClientId\u002FSecret are validated on startup. Missing values fail fast.",[209,769,770,773,774,777],{},[218,771,772],{},"AddExternalAuthEndpoints\u003CTUser>()"," returns an ",[218,775,776],{},"ExternalAuthBuilder",". These methods extend it:",[779,780,781,799,809],"ul",{},[782,783,784,786,787,789,790,786,792,794,795,798],"li",{},[218,785,604],{}," (from ",[218,788,224],{},") \u002F ",[218,791,691],{},[218,793,228],{},") — OAuth handlers (",[218,796,797],{},"SignInScheme = Identity.External",") + provider metadata",[782,800,801,804,805,808],{},[218,802,803],{},"AddCompleter\u003CT>"," — replace cookie completion (e.g. ",[218,806,807],{},"JwtExternalLoginCompleter\u003CAppUser>",")",[782,810,811,348,814,817,818,808],{},[218,812,813],{},"AddProvider",[218,815,816],{},"AddProvider\u003CT>"," — custom providers (see ",[239,819,821],{"href":820},"#custom-providers","Custom providers",[823,824,826],"h3",{"id":825},"jwt-completion","JWT completion",[209,828,829,830,834,835,838],{},"Requires ",[239,831,833],{"href":832},"\u002Fmodules\u002Fjwt\u002F","JWT"," services registered (",[218,836,837],{},"AddJwtEndpoints","):",[270,840,842],{"className":383,"code":841,"language":385,"meta":275,"style":275},"builder.Services.AddExternalAuthEndpoints\u003CAppUser>()\n    .AddCompleter\u003CJwtExternalLoginCompleter\u003CAppUser>>()\n    .AddGitHub(...);\n",[218,843,844,863,883],{"__ignoreMap":275},[279,845,846,848,850,852,854,856,858,860],{"class":281,"line":282},[279,847,458],{"class":396},[279,849,243],{"class":400},[279,851,463],{"class":396},[279,853,243],{"class":400},[279,855,375],{"class":468},[279,857,471],{"class":400},[279,859,474],{"class":285},[279,861,862],{"class":400},">()\n",[279,864,865,868,871,873,876,878,880],{"class":281,"line":299},[279,866,867],{"class":400},"    .",[279,869,870],{"class":468},"AddCompleter",[279,872,471],{"class":400},[279,874,875],{"class":285},"JwtExternalLoginCompleter",[279,877,471],{"class":400},[279,879,474],{"class":285},[279,881,882],{"class":400},">>()\n",[279,884,885,887,889,892,894],{"class":281,"line":314},[279,886,867],{"class":400},[279,888,604],{"class":468},[279,890,891],{"class":400},"(..",[279,893,243],{"class":396},[279,895,896],{"class":400},");\n",[209,898,899,900,903],{},"After OAuth, a refresh cookie is written and the browser redirects to ",[218,901,902],{},"returnUrl",". The client should obtain an access token via the JWT refresh flow (CSRF + refresh cookie) — the access token is not placed in the redirect URL.",[265,905,907],{"id":906},"map","Map",[270,909,911],{"className":383,"code":910,"language":385,"meta":275,"style":275},"var external = app.MapGroup(\"\u002Fauth\u002Fexternal\").WithTags(\"External\");\nexternal.MapGitHubAuthEndpoints\u003CAppUser>();\nexternal.MapGoogleAuthEndpoints\u003CAppUser>();\nexternal.MapExternalAccountEndpoints\u003CAppUser>(); \u002F\u002F link \u002F unlink while signed in\n",[218,912,913,957,974,989],{"__ignoreMap":275},[279,914,915,918,921,924,927,929,932,934,936,939,941,944,947,949,951,953,955],{"class":281,"line":282},[279,916,917],{"class":285},"var",[279,919,920],{"class":285}," external",[279,922,923],{"class":400}," =",[279,925,926],{"class":396}," app",[279,928,243],{"class":400},[279,930,931],{"class":468},"MapGroup",[279,933,607],{"class":400},[279,935,555],{"class":400},[279,937,938],{"class":289},"\u002Fauth\u002Fexternal",[279,940,555],{"class":400},[279,942,943],{"class":400},").",[279,945,946],{"class":468},"WithTags",[279,948,607],{"class":400},[279,950,555],{"class":400},[279,952,403],{"class":289},[279,954,555],{"class":400},[279,956,896],{"class":400},[279,958,959,962,964,967,969,971],{"class":281,"line":299},[279,960,961],{"class":396},"external",[279,963,243],{"class":400},[279,965,966],{"class":468},"MapGitHubAuthEndpoints",[279,968,471],{"class":400},[279,970,474],{"class":285},[279,972,973],{"class":400},">();\n",[279,975,976,978,980,983,985,987],{"class":281,"line":314},[279,977,961],{"class":396},[279,979,243],{"class":400},[279,981,982],{"class":468},"MapGoogleAuthEndpoints",[279,984,471],{"class":400},[279,986,474],{"class":285},[279,988,973],{"class":400},[279,990,991,993,995,998,1000,1002,1005],{"class":281,"line":448},[279,992,961],{"class":396},[279,994,243],{"class":400},[279,996,997],{"class":468},"MapExternalAccountEndpoints",[279,999,471],{"class":400},[279,1001,474],{"class":285},[279,1003,1004],{"class":400},">();",[279,1006,1007],{"class":513}," \u002F\u002F link \u002F unlink while signed in\n",[209,1009,1010],{},"Or map every registered sign-in provider:",[270,1012,1014],{"className":383,"code":1013,"language":385,"meta":275,"style":275},"app.MapGroup(\"\u002Fauth\u002Fexternal\").MapExternalAuthEndpoints\u003CAppUser>();\n",[218,1015,1016],{"__ignoreMap":275},[279,1017,1018,1021,1023,1025,1027,1029,1031,1033,1035,1038,1040,1042],{"class":281,"line":282},[279,1019,1020],{"class":396},"app",[279,1022,243],{"class":400},[279,1024,931],{"class":468},[279,1026,607],{"class":400},[279,1028,555],{"class":400},[279,1030,938],{"class":289},[279,1032,555],{"class":400},[279,1034,943],{"class":400},[279,1036,1037],{"class":468},"MapExternalAuthEndpoints",[279,1039,471],{"class":400},[279,1041,474],{"class":285},[279,1043,973],{"class":400},[265,1045,1047],{"id":1046},"routes","Routes",[209,1049,1050,1051,838],{},"Relative to the group prefix (example ",[218,1052,938],{},[1054,1055,1056,1075],"table",{},[1057,1058,1059],"thead",{},[1060,1061,1062,1066,1069,1072],"tr",{},[1063,1064,1065],"th",{},"Method",[1063,1067,1068],{},"Path",[1063,1070,1071],{},"Auth",[1063,1073,1074],{},"Notes",[1076,1077,1078,1098,1117,1133,1149,1166,1183,1199],"tbody",{},[1060,1079,1080,1086,1091,1093],{},[1081,1082,1083],"td",{},[218,1084,1085],{},"GET",[1081,1087,1088],{},[218,1089,1090],{},"\u002Flogin\u002Fgithub",[1081,1092],{},[1081,1094,1095,1096],{},"Challenge; rate-limited; ",[218,1097,902],{},[1060,1099,1100,1104,1109,1111],{},[1081,1101,1102],{},[218,1103,1085],{},[1081,1105,1106],{},[218,1107,1108],{},"\u002Flogin\u002Fgithub\u002Fcallback",[1081,1110],{},[1081,1112,1113,1114],{},"Provision + completer; errors redirect to ",[218,1115,1116],{},"ErrorPath",[1060,1118,1119,1123,1128,1130],{},[1081,1120,1121],{},[218,1122,1085],{},[1081,1124,1125],{},[218,1126,1127],{},"\u002Flogin\u002Fgoogle",[1081,1129],{},[1081,1131,1132],{},"Challenge; rate-limited",[1060,1134,1135,1139,1144,1146],{},[1081,1136,1137],{},[218,1138,1085],{},[1081,1140,1141],{},[218,1142,1143],{},"\u002Flogin\u002Fgoogle\u002Fcallback",[1081,1145],{},[1081,1147,1148],{},"Provision + completer",[1060,1150,1151,1155,1160,1163],{},[1081,1152,1153],{},[218,1154,1085],{},[1081,1156,1157],{},[218,1158,1159],{},"\u002Flogins",[1081,1161,1162],{},"yes",[1081,1164,1165],{},"List linked external logins",[1060,1167,1168,1173,1178,1180],{},[1081,1169,1170],{},[218,1171,1172],{},"DELETE",[1081,1174,1175],{},[218,1176,1177],{},"\u002Flogins\u002F{loginProvider}\u002F{providerKey}",[1081,1179,1162],{},[1081,1181,1182],{},"Unlink; antiforgery + ReAuth; refuses the last sign-in method",[1060,1184,1185,1189,1194,1196],{},[1081,1186,1187],{},[218,1188,1085],{},[1081,1190,1191],{},[218,1192,1193],{},"\u002Flink\u002F{scheme}",[1081,1195,1162],{},[1081,1197,1198],{},"Start link challenge",[1060,1200,1201,1205,1210,1212],{},[1081,1202,1203],{},[218,1204,1085],{},[1081,1206,1207],{},[218,1208,1209],{},"\u002Flink\u002F{scheme}\u002Fcallback",[1081,1211,1162],{},[1081,1213,1214],{},"Complete link",[823,1216,1218],{"id":1217},"idp-callback-paths","IdP callback paths",[1054,1220,1221,1231],{},[1057,1222,1223],{},[1060,1224,1225,1228],{},[1063,1226,1227],{},"Provider",[1063,1229,1230],{},"Middleware path",[1076,1232,1233,1242],{},[1060,1234,1235,1237],{},[1081,1236,426],{},[1081,1238,1239],{},[218,1240,1241],{},"\u002Fsignin-github",[1060,1243,1244,1246],{},[1081,1245,443],{},[1081,1247,1248],{},[218,1249,1250],{},"\u002Fsignin-google",[265,1252,1254],{"id":1253},"security-behavior","Security behavior",[779,1256,1257,1275,1308,1313,1326,1355,1361,1391,1409],{},[782,1258,1259,1262,1263,1266,1267,1270,1271,1274],{},[213,1260,1261],{},"Verified email (default):"," new accounts require a verified provider email. Google reads ",[218,1264,1265],{},"email_verified"," from the userinfo payload after the host configures the handler, so a host stamp cannot stick. GitHub calls ",[218,1268,1269],{},"GET https:\u002F\u002Fapi.github.com\u002Fuser\u002Femails"," with the access token and keeps the verified primary address, or any verified address when the primary is unverified. An unverified profile email is dropped. The access token is not saved on the ticket (",[218,1272,1273],{},"SaveTokens"," is false).",[782,1276,1277,1280,1281,1284,1285,1288,1289,1292,1293,1296,1297,1300,1301,1304,1305,243],{},[213,1278,1279],{},"Auto-link by email (default off):"," when ",[218,1282,1283],{},"AutoLinkByEmail"," is true, the provider email must be verified ",[213,1286,1287],{},"and"," the local account's ",[218,1290,1291],{},"EmailConfirmed"," must be true, even if ",[218,1294,1295],{},"RequireVerifiedEmail"," is false. Otherwise the callback denies with ",[218,1298,1299],{},"auto_link_disabled",", ",[218,1302,1303],{},"email_unverified",", or ",[218,1306,1307],{},"email_unconfirmed",[782,1309,1310,1312],{},[213,1311,1291],{}," on new users matches whether the provider email was verified.",[782,1314,1315,1318,1319,1322,1323,1325],{},[213,1316,1317],{},"Link:"," the challenge stores the signed-in user id as the external-login XSRF token (",[218,1320,1321],{},"ConfigureExternalAuthenticationProperties","). The callback accepts the external login only when that token matches. Explicit link also requires a verified email when ",[218,1324,1295],{}," is true. The callback scheme must match the route.",[782,1327,1328,1331,1332,1335,1336,1339,1340,1343,1344,1347,1348,1351,1352,1354],{},[213,1329,1330],{},"Unlink:"," requires an authenticated application user, the core antiforgery filter, and a ReAuth principal (",[218,1333,1334],{},"AuthEndpoints.ReAuth"," or ",[218,1337,1338],{},"AuthEndpoints.ReAuth.Bearer"," with claim ",[218,1341,1342],{},"Reauth=true","). ReAuth is checked without replacing ",[218,1345,1346],{},"HttpContext.User",", so the antiforgery token still matches the application user. Removal is refused when it would delete the last sign-in method (a password, a passkey, or another external login must remain). A missing login returns 404. The problem title is ",[218,1349,1350],{},"last_signin_method",". Passkeys are counted when the Identity store schema is version 3 (",[218,1353,356],{}," sets this).",[782,1356,1357,1360],{},[213,1358,1359],{},"External cookie"," is signed out after success and on login\u002Flink failure, including remote authentication failure.",[782,1362,1363,1368,1369,1371,1372,1375,1376,1379,1380,1383,1384,1387,1388,243],{},[213,1364,1365,1367],{},[218,1366,902],{},":"," rooted local paths are accepted before any absolute-URI parse, so ",[218,1370,552],{}," and ",[218,1373,1374],{},"\u002Fdashboard"," stay local on Linux. ",[218,1377,1378],{},"~\u002F",", protocol-relative ",[218,1381,1382],{},"\u002F\u002F",", backslash, control characters, and encoded CR\u002FLF\u002FNUL\u002Ftab are rejected. Absolute URLs must match ",[218,1385,1386],{},"AllowedReturnUrlOrigins"," by scheme, host, and port (same rule as email-confirmation redirects). Illegal values fall back to ",[218,1389,1390],{},"DefaultReturnUrl",[782,1392,1393,1396,1397,1400,1401,1404,1405,1408],{},[213,1394,1395],{},"Errors \u002F cancel:"," browser redirects to ",[218,1398,1399],{},"ErrorPath?error=&error_description=","; clients that prefer ",[218,1402,1403],{},"application\u002Fjson"," over ",[218,1406,1407],{},"text\u002Fhtml"," get Problem details instead.",[782,1410,1411,1414,1415,1418,1419,1422,1423,1426,1427,1429],{},[213,1412,1413],{},"Cookie completion"," calls ",[218,1416,1417],{},"SignInAsync"," directly. It does not issue a two-factor challenge, so OAuth sign-in skips 2FA for users who turned it on. Identity UI uses ",[218,1420,1421],{},"ExternalLoginSignInAsync"," instead, which returns ",[218,1424,1425],{},"RequiresTwoFactor"," for those users. ",[218,1428,875],{}," also skips 2FA. Hosts that need 2FA after OAuth should register a completer that checks it.",[265,1431,1433],{"id":1432},"options","Options",[1054,1435,1436,1448],{},[1057,1437,1438],{},[1060,1439,1440,1443,1446],{},[1063,1441,1442],{},"Property",[1063,1444,1445],{},"Default",[1063,1447,1074],{},[1076,1449,1450,1465,1480,1493,1506,1520,1532],{},[1060,1451,1452,1457,1462],{},[1081,1453,1454],{},[218,1455,1456],{},"SignInScheme",[1081,1458,1459],{},[218,1460,1461],{},"null",[1081,1463,1464],{},"Cookie completer scheme override",[1060,1466,1467,1472,1477],{},[1081,1468,1469],{},[218,1470,1471],{},"IsPersistent",[1081,1473,1474],{},[218,1475,1476],{},"true",[1081,1478,1479],{},"Application cookie persistence",[1060,1481,1482,1486,1490],{},[1081,1483,1484],{},[218,1485,1390],{},[1081,1487,1488],{},[218,1489,552],{},[1081,1491,1492],{},"Rooted local path",[1060,1494,1495,1499,1503],{},[1081,1496,1497],{},[218,1498,1295],{},[1081,1500,1501],{},[218,1502,1476],{},[1081,1504,1505],{},"Block unverified provider emails on create and explicit link",[1060,1507,1508,1512,1517],{},[1081,1509,1510],{},[218,1511,1283],{},[1081,1513,1514],{},[218,1515,1516],{},"false",[1081,1518,1519],{},"Opt-in link of an existing confirmed user by verified provider email",[1060,1521,1522,1526,1529],{},[1081,1523,1524],{},[218,1525,1386],{},[1081,1527,1528],{},"empty",[1081,1530,1531],{},"Absolute http(s) origins allowlist",[1060,1533,1534,1538,1542],{},[1081,1535,1536],{},[218,1537,1116],{},[1081,1539,1540],{},[218,1541,577],{},[1081,1543,1544],{},"Relative path for error redirects",[265,1546,821],{"id":1547},"custom-providers",[1549,1550,1551,1557,1564],"ol",{},[782,1552,1553,1554,243],{},"Register an OAuth handler with ",[218,1555,1556],{},"SignInScheme = IdentityConstants.ExternalScheme",[782,1558,1559,1560,1563],{},"Implement ",[218,1561,1562],{},"IExternalAuthProvider"," (scheme + login\u002Fcallback paths + endpoint name).",[782,1565,1566,1569,1570,1335,1573,243],{},[218,1567,1568],{},"builder.AddProvider\u003CMyProvider>()"," then ",[218,1571,1572],{},"MapExternalAuthProvider\u003CTUser>(\"MyScheme\")",[218,1574,1037],{},[209,1576,1577,1578,1580,1581,1583,1584,1586],{},"Built-in GitHub and Google providers set email and ",[218,1579,1265],{}," themselves. A custom provider must put a real verified email on the principal when ",[218,1582,1295],{}," is true. A host event that stamps ",[218,1585,1265],{}," before the provider seal does not count.",[265,1588,636],{"id":1589},"configuration",[209,1591,1592,1593,1371,1596,1599,1600,1603,1604,1335,1606,1608,1609,1612],{},"The library reads no configuration keys. OAuth ",[218,1594,1595],{},"ClientId",[218,1597,1598],{},"ClientSecret"," come from the ",[218,1601,1602],{},"configure"," delegate that you pass to ",[218,1605,604],{},[218,1607,691],{},". They are not ",[218,1610,1611],{},"ExternalAuthOptions"," properties. The DI sample above reads them from host configuration:",[1054,1614,1615,1625],{},[1057,1616,1617],{},[1060,1618,1619,1622],{},[1063,1620,1621],{},"Configuration key",[1063,1623,1624],{},"Environment variable",[1076,1626,1627,1643],{},[1060,1628,1629,1635],{},[1081,1630,1631,348,1633],{},[218,1632,644],{},[218,1634,674],{},[1081,1636,1637,348,1640],{},[218,1638,1639],{},"Authentication__GitHub__ClientId",[218,1641,1642],{},"Authentication__GitHub__ClientSecret",[1060,1644,1645,1651],{},[1081,1646,1647,348,1649],{},[218,1648,726],{},[218,1650,754],{},[1081,1652,1653,348,1656],{},[218,1654,1655],{},"Authentication__Google__ClientId",[218,1657,1658],{},"Authentication__Google__ClientSecret",[209,1660,1661,1662,1664],{},"Use any key names you like. Change the ",[218,1663,1602],{}," delegate to match.",[265,1666,1668],{"id":1667},"gotchas","Gotchas",[779,1670,1671,1679,1685,1693],{},[782,1672,1673,1674,1676,1677,943],{},"Pipeline: authentication + ",[218,1675,367],{}," (via ",[218,1678,371],{},[782,1680,1681,1682,1684],{},"Host an error page at ",[218,1683,1116],{}," (or change the option).",[782,1686,1687,1688,1335,1690,1692],{},"Install ",[218,1689,224],{},[218,1691,228],{}," (or both). The core package alone does not register those handlers.",[782,1694,1695,1696,243],{},"OAuth sign-in skips two-factor with both built-in completers. See ",[239,1697,1254],{"href":1698},"#security-behavior",[265,1700,1702],{"id":1701},"related","Related",[779,1704,1705,1711,1715,1720],{},[782,1706,1707],{},[239,1708,1710],{"href":1709},"\u002Fmodules\u002Fcookie-auth\u002F","Cookie auth",[782,1712,1713],{},[239,1714,833],{"href":832},[782,1716,1717],{},[239,1718,97],{"href":1719},"\u002Fcomposables\u002F",[782,1721,1722],{},[239,1723,1725],{"href":1724},"\u002Fgetting-started\u002Finstallation\u002F","Installation",[1727,1728,1729],"style",{},"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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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":275,"searchDepth":282,"depth":299,"links":1731},[1732,1733,1736,1737,1740,1741,1742,1743,1744,1745],{"id":267,"depth":299,"text":268},{"id":379,"depth":299,"text":380,"children":1734},[1735],{"id":825,"depth":314,"text":826},{"id":906,"depth":299,"text":907},{"id":1046,"depth":299,"text":1047,"children":1738},[1739],{"id":1217,"depth":314,"text":1218},{"id":1253,"depth":299,"text":1254},{"id":1432,"depth":299,"text":1433},{"id":1547,"depth":299,"text":821},{"id":1589,"depth":299,"text":636},{"id":1667,"depth":299,"text":1668},{"id":1701,"depth":299,"text":1702},"Separate preview package for GitHub and Google OAuth login with Identity cookie or JWT completion.","md",null,{},{"icon":49},{"title":154,"description":1746},"qVy5ZBlk4U4hsM6Bq3ICrkdM-Xy3dRskZtgtHsCA2xY",[1754,1756],{"title":149,"path":150,"stem":151,"description":1755,"icon":152,"children":-1},"Step-up reauthentication for sensitive manage and passkey actions.",{"title":158,"path":159,"stem":160,"description":1757,"icon":161,"children":-1},"Reference for AuthEndpointsOptions and nested passkey, JWT, ReAuth, and email confirmation settings.",1791123628696]