.NET 8 Web 应用中的身份验证允许 1) 实体框架/标识以及 2) 使用 Microsoft Entra/AAD 和 Google 的 SSO - 不能同时执行 1 和 2

Authentication in .NET 8 web app allowing 1) Entity Framework / Identity as well as 2) SSO using Microsoft Entra/AAD, and Google - can't do both 1 & 2

提问人:user5974635 提问时间:11/17/2023 最后编辑:marc_suser5974635 更新时间:11/17/2023 访问量:55

问:

我有一个 .NET 8 Web 应用程序,我希望允许使用实体框架/标识进行身份验证,以及使用 Microsoft Entra/Azure Active Directory (AAD) 和 Google 的 SSO。

我可以让这三者都工作,但不能同时工作。要么 Identity 有效,要么 Entra 和 Google 无效;或者身份不起作用,而 Entra 和 Google 可以工作。

问题不在于身份验证本身,因为它似乎总是有效,它只是不记得它已登录以进行下一个请求。Cookie 和/或身份验证方案发生了什么?

以下是以下部分:Program.cs

// Identity (Entity Framework)
// This won't work if Google and Azure below are active
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
    {
        options.SignIn.RequireConfirmedAccount = false;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>();

// This works but breaks Google and Azure
//builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = false)
//    .AddEntityFrameworkStores<ApplicationDbContext>()
//    .AddDefaultTokenProviders()
//    .AddSignInManager<SignInManager<ApplicationUser>>();

// Authentication (Azure)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
    .AddInMemoryTokenCaches();

// Authentication (Google)
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddGoogle(googleOptions =>
    {
        googleOptions.ClientId = AppConfig.GetValue("Google:ClientId");
        googleOptions.ClientSecret = AppConfig.GetValue("Google:ClientSecret");
        googleOptions.Events = new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents
        {
            OnRedirectToAuthorizationEndpoint = context =>
            {
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            }
        };
    });

以下是处理使用 Entra 或 Google 登录的方法。只需调用 Challenge 方法,对于 Google,在我得到响应后还有一些工作要做。

[HttpGet, HttpPost, ViewPath]
[Route("login/entra")]
public ActionResult LoginEntra()
{
    if (CurrentUser != null)
        return Redirect("/");

    return Challenge(
            new AuthenticationProperties { RedirectUri = "/" },
            OpenIdConnectDefaults.AuthenticationScheme);
}

[HttpGet, HttpPost, ViewPath]
[Route("login/google")]
public ActionResult LoginGoogle()
{
    if (CurrentUser != null)
        return Redirect("/");

    return Challenge(
            new AuthenticationProperties { RedirectUri = "/login/google-response", AllowRefresh = true },
            GoogleDefaults.AuthenticationScheme);
}

[HttpGet, HttpPost, ViewPath]
[Route("login/google-response")]
public async Task<IActionResult> LoginGoogleResponse()
{
    string authScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);

    if (result?.Succeeded == true)
    {
        var claimsIdentity = new ClaimsIdentity(result.Principal.Claims, authScheme, ClaimTypes.Name, ClaimTypes.Role);
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
        var authProperties = new AuthenticationProperties
            {
                IsPersistent = true
            };

        await HttpContext.SignInAsync(authScheme, new ClaimsPrincipal(claimsIdentity), authProperties);

        // Redirect to the desired page after login
        return Redirect("/");
    }
    else
    {
        // The user might not be authenticated, handle accordingly
        return Challenge(GoogleDefaults.AuthenticationScheme);
    }
}

最后,这是当用户使用电子邮件和密码进行身份验证时调用的 API 方法。我自己处理密码加密,所以我还需要验证登录凭据。UserDAO 是我的自定义类,它连接到数据库以执行与用户相关的功能并存储“用户”对象(那里没有疯狂的事情)。

[HttpPost, ApiRequest]
[Route("api/Account/Login")]
public async Task<ActionResult> Login(string email, string password, bool rememberMe = true)
{
    try
    {
        string authScheme = IdentityConstants.ApplicationScheme;

        // Retrieve the user by their email
        ApplicationUser user = await UserManager.FindByEmailAsync(email);

        if (user == null)
            throw new UnauthorizedAccessException("Invalid email address or password.");

        // Verify the user's password
        bool isPasswordValid = UserDAO.VerifyPassword(email, password);

        if (!isPasswordValid)
            throw new UnauthorizedAccessException("Invalid email address or password.");

        UserDAO userObj = new UserDAO(email);

        // The password is valid, so sign in the user
        // This bypasses the built-in password check and signs in the user directly
        await SignInManager.SignInAsync(user, true);

        // bool test = SignInManager.IsSignedIn(???);

        // Set a cookie to remember the email for the form
        if (rememberMe)
            Response.Cookies.Append("User.Email", email);

        // Determine the redirect URL
        string redirect = "/";

        // Return success
        return JsonWebResult.Success(new { Redirect = redirect });
    }
    catch (Exception ex)
    {
        return JsonWebResult.Error(ex.Message);
    }
}

我尝试不指定 Entra 和 Google 的身份验证方案。然后 EF / Identity 有效,但 Entra 和 Google 不起作用。

我还尝试使用 AddIdentity 而不是 AddDefaultIdentity。这奏效了,但破坏了 Entra 和 Google。

基本上,我尝试过的所有使 Identity 发挥作用的东西都破坏了 Entra 和 Google。

同样,身份验证本身始终有效 - 它只是不“记住”用户已登录。如果我在 Identity 工作时使用 Entra 登录,它将成功进行身份验证,但在下一个请求中将为 false。如果我更改代码并再次运行它,确保 Entra 正常运行(并破坏 Identity),它实际上显示我已登录到 Entra。CurrentUser.Identity.IsAuthenticated

实体框架 身份验证 active-directory asp.net-identity net-8.0

评论


答: 暂无答案