External OAuth
External OAuth (GitHub, Google) ships as a separate NuGet package: AuthEndpoints.External.OAuth. It is compose-only (not wired into the AddAuthEndpoints facade). Default completion issues an Identity application cookie; replace the completer for JWT.
AuthEndpoints package — see the changelog. This package always references both GitHub and Google OAuth handler packages; per-provider NuGet splits may come later.Install
dotnet add package AuthEndpoints --version 3.0.0-rc.3
dotnet add package AuthEndpoints.External.OAuth --version 3.0.0-preview.2
Requires ASP.NET Core Identity (UserManager / SignInManager) already configured — typically via AddAuthEndpoints. It does not call Identity management HTTP APIs; those remain optional alongside External.
AuthEndpoints.External.OAuth 3.0.0-preview.2+ requires AuthEndpoints 3.0.0-rc.3+ (public AddLoginRateLimiting used by OAuth DI).
Hosts must call UseRateLimiter() (included in UseAuthEndpoints). AddExternalAuthEndpoints registers the login rate-limit policy.
DI
using AuthEndpoints.External.OAuth;
using AuthEndpoints.External.OAuth.GitHub;
using AuthEndpoints.External.OAuth.Google;
builder.Services.AddExternalAuthEndpoints<AppUser>(o =>
{
o.RequireVerifiedEmail = true; // default
o.AutoLinkByEmail = true; // default; only when email is verified
o.DefaultReturnUrl = "/"; // must be relative
o.ErrorPath = "/auth/external/error";
// o.AllowedReturnUrlOrigins.Add("https://app.example.com"); // optional absolute allowlist
})
.AddGitHub(o =>
{
o.ClientId = builder.Configuration["Authentication:GitHub:ClientId"]!;
o.ClientSecret = builder.Configuration["Authentication:GitHub:ClientSecret"]!;
})
.AddGoogle(o =>
{
o.ClientId = builder.Configuration["Authentication:Google:ClientId"]!;
o.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]!;
});
ClientId/Secret are validated on startup. Missing values fail fast.
AddExternalAuthBuilder methods:
AddGitHub/AddGoogle— OAuth handlers (SignInScheme = Identity.External) + provider metadataAddCompleter<T>— replace cookie completion (e.g.JwtExternalLoginCompleter<AppUser>)AddProvider/AddProvider<T>— custom providers (see Custom providers)
JWT completion
Requires JWT services registered (AddJwtEndpoints):
builder.Services.AddExternalAuthEndpoints<AppUser>()
.AddCompleter<JwtExternalLoginCompleter<AppUser>>()
.AddGitHub(...);
After OAuth, a refresh cookie is written and the browser redirects to returnUrl. The SPA should obtain an access token via the JWT refresh flow (CSRF + refresh cookie) — the access token is not placed in the redirect URL.
Map
var external = app.MapGroup("/auth/external").WithTags("External");
external.MapGitHubAuthEndpoints<AppUser>();
external.MapGoogleAuthEndpoints<AppUser>();
external.MapExternalAccountEndpoints<AppUser>(); // link / unlink while signed in
Or map every registered sign-in provider:
app.MapGroup("/auth/external").MapExternalAuthEndpoints<AppUser>();
Routes
Relative to the group prefix (example /auth/external):
| Method | Path | Auth | Notes |
|---|---|---|---|
GET | /login/github | Challenge; rate-limited; returnUrl | |
GET | /login/github/callback | Provision + completer; errors redirect to ErrorPath | |
GET | /login/google | Challenge; rate-limited | |
GET | /login/google/callback | Provision + completer | |
GET | /logins | yes | List linked external logins |
DELETE | /logins/{loginProvider}/{providerKey} | yes | Unlink |
GET | /link/{scheme} | yes | Start link challenge |
GET | /link/{scheme}/callback | yes | Complete link |
IdP callback paths
| Provider | Middleware path |
|---|---|
| GitHub | /signin-github |
/signin-google |
Security behavior
- Verified email (default): create/link requires
email_verified=true(Google/OIDC). GitHub marks email verified when the handler surfaces an email viauser:email(verified primary). - Auto-link by email (default on): only when the email is verified; disable with
AutoLinkByEmail = false. - EmailConfirmed on new users matches whether the provider email was verified.
- External cookie is signed out after successful app sign-in / link.
returnUrl: relative local paths only by default; absolute URLs needAllowedReturnUrlOrigins.- Errors / cancel: browser redirects to
ErrorPath?error=&error_description=; clients that preferapplication/jsonovertext/htmlget Problem details instead.
Options
| Property | Default | Notes |
|---|---|---|
SignInScheme | null | Cookie completer scheme override |
IsPersistent | true | Application cookie persistence |
DefaultReturnUrl | / | Relative local path |
RequireVerifiedEmail | true | Block unverified provider emails |
AutoLinkByEmail | true | Link existing user by verified email |
AllowedReturnUrlOrigins | empty | Absolute http(s) origins allowlist |
ErrorPath | /auth/external/error | Relative path for error redirects |
Custom providers
- Register an OAuth handler with
SignInScheme = IdentityConstants.ExternalScheme. - Implement
IExternalAuthProvider(scheme + login/callback paths + endpoint name). builder.AddProvider<MyProvider>()thenMapExternalAuthProvider<TUser>("MyScheme")orMapExternalAuthEndpoints.
Ensure the provider principal includes email + email_verified when RequireVerifiedEmail is true.
Configuration (Demo)
| Variable | Provider |
|---|---|
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | GitHub |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Gotchas
- Pipeline: authentication +
UseRateLimiter()(viaUseAuthEndpoints). - Host an error page at
ErrorPath(or change the option). - Package split for GitHub-only / Google-only NuGets is deferred; both handler packages ship with this preview.