This commit is contained in:
rossmill76 committed 2026-03-02 11:50:08 +00:00
1 parent 2cf6128a85
commit 45a5e3fc5e
11 files changed
+428 -40

No files matched your search

+100
View File
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using BudgetApp.Components;
@@ -34,6 +35,47 @@ builder.Services.ConfigureApplicationCookie(options =>
options.LogoutPath = "/logout";
});
// External authentication providers (only registered when configured)
var authBuilder = builder.Services.AddAuthentication();
var googleSection = builder.Configuration.GetSection("Authentication:Google");
if (!string.IsNullOrEmpty(googleSection["ClientId"]) && !string.IsNullOrEmpty(googleSection["ClientSecret"]))
{
authBuilder.AddGoogle(options =>
{
options.ClientId = googleSection["ClientId"]!;
options.ClientSecret = googleSection["ClientSecret"]!;
});
}
var microsoftSection = builder.Configuration.GetSection("Authentication:Microsoft");
if (!string.IsNullOrEmpty(microsoftSection["ClientId"]) && !string.IsNullOrEmpty(microsoftSection["ClientSecret"]))
{
authBuilder.AddMicrosoftAccount(options =>
{
options.ClientId = microsoftSection["ClientId"]!;
options.ClientSecret = microsoftSection["ClientSecret"]!;
});
}
var githubSection = builder.Configuration.GetSection("Authentication:GitHub");
if (!string.IsNullOrEmpty(githubSection["ClientId"]) && !string.IsNullOrEmpty(githubSection["ClientSecret"]))
{
authBuilder.AddOAuth("GitHub", "GitHub", options =>
{
options.ClientId = githubSection["ClientId"]!;
options.ClientSecret = githubSection["ClientSecret"]!;
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.CallbackPath = "/signin-github";
options.Scope.Add("user:email");
options.ClaimActions.MapJsonKey(System.Security.Claims.ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(System.Security.Claims.ClaimTypes.Name, "login");
options.ClaimActions.MapJsonKey(System.Security.Claims.ClaimTypes.Email, "email");
});
}
// Application services
builder.Services.AddScoped<AccountService>();
builder.Services.AddScoped<TransactionService>();
@@ -75,4 +117,62 @@ app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// External authentication endpoints
app.MapPost("/api/auth/external-login", (HttpContext context) =>
{
var provider = context.Request.Form["provider"].ToString();
var returnUrl = context.Request.Form["returnUrl"].ToString();
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "/";
var redirectUrl = $"/api/auth/external-callback?returnUrl={Uri.EscapeDataString(returnUrl)}";
var properties = new Microsoft.AspNetCore.Authentication.AuthenticationProperties { RedirectUri = redirectUrl };
return Results.Challenge(properties, [provider]);
});
app.MapGet("/api/auth/external-callback", async (
HttpContext context,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
string? returnUrl) =>
{
returnUrl ??= "/";
var info = await signInManager.GetExternalLoginInfoAsync();
if (info is null)
return Results.Redirect("/login");
var signInResult = await signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false);
if (signInResult.Succeeded)
return Results.Redirect(returnUrl);
// Auto-register: create user from external claims
var email = info.Principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
var userName = email ?? info.Principal.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value
?? $"{info.LoginProvider}_{info.ProviderKey}";
var user = new ApplicationUser { UserName = userName, Email = email };
var createResult = await userManager.CreateAsync(user);
if (createResult.Succeeded)
{
await userManager.AddLoginAsync(user, info);
await signInManager.SignInAsync(user, isPersistent: false);
return Results.Redirect(returnUrl);
}
// If the email is already taken, try linking
if (email is not null)
{
var existingUser = await userManager.FindByEmailAsync(email);
if (existingUser is not null)
{
await userManager.AddLoginAsync(existingUser, info);
await signInManager.SignInAsync(existingUser, isPersistent: false);
return Results.Redirect(returnUrl);
}
}
return Results.Redirect("/login");
});
app.Run();