如何在具有单独 API 和单独前端应用程序的应用程序中最好地在 .NET 7 中实现 Google 身份验证?

问题描述 投票:0回答:1

我有一个 .NET 7 应用程序,它有 2 个独立的项目。一个项目是包含所有后端代码的 API,另一个项目是主要包含 javascript 的前端。 API 和前端在不同的 URL 上运行。在 API 中,我们实现了 IdentityServer4 来进行 OAUTH2 身份验证。用户可以通过前端使用用户名和密码登录,然后前端调用API来请求访问令牌。然后,此令牌将添加到对 API 的所有后续请求的授权标头中。

我正在尝试实现允许用户通过其 Google 帐户登录的功能,而不是用户名和密码。我添加了一个到前端的链接,它将用户重定向到 API 的

external-login
端点。该端点是一个控制器函数,它返回一个以 Google 作为提供者的 ChallengeResult。它还具有一个回调 URL,用于在用户通过 Google 进行身份验证后将用户重定向回 API 中的回调端点,这将验证所有内容,然后将用户重定向到前端应用程序。到目前为止,这一切都有效,我可以通过 Google 进行身份验证,一切都经过验证,然后我会被发送回我的前端。

我遇到的问题是前端不知道用户已通过Google成功登录。当用户通过用户名/密码登录时,我调用 IdentityServer4 的 /token 端点,并返回带有 JWT 令牌、过期日期等的结果。我将其保存在浏览器的本地存储中,并使用它来验证所有其他请求到 API。当我通过 Google 对用户进行身份验证时,不会发生这种情况,因为这适用于重定向。因此前端不会收到 JWT 令牌,因此无法验证对 API 的请求。

我这样做是因为我发现的一切都告诉我这样做,我还没有找到任何其他方法来做到这一点。我很难找到正确解释正在发生的事情以及如何自定义该代码的示例。

我想我可能这样做完全错误,也许我还需要调用 /token 端点进行 Google 身份验证,但我不确定如何准确执行此操作,我找不到任何示例。谁能给我解释一下吗?

这是我目前拥有的API中的代码(其中很多代码来自我在IdentityServer4文档中找到的示例,我还不知道我实际需要多少代码):

[Route("api/v3/[controller]")]
[ApiController]
[Consumes(MediaTypeNames.Application.Json)]
[Produces(MediaTypeNames.Application.Json)]
public class UsersController : ControllerBase
{
    /// <summary>
    /// Endpoint for external login providers such as Google.
    /// This uses IdentityServer4 to handle the authentication.
    /// </summary>
    /// <param name="provider"></param>
    /// <returns></returns>
    [HttpGet]
    [Route("external-login")]
    [AllowAnonymous]
    public IActionResult ExternalLogin(string provider)
    {
        var properties = new AuthenticationProperties
        {
            RedirectUri = Url.Action(nameof(Callback)),
            AllowRefresh = true,
            IsPersistent = true
        };

        return Challenge(properties, provider);
    }
    
    [HttpGet]
    [Route("external-login-callback")]
    [AllowAnonymous]
    public async Task<IActionResult> Callback()
    {
        // read external identity from the temporary cookie
        var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
        if (result?.Succeeded != true)
        {
            throw new Exception("External authentication error");
        }

        var externalUser = result.Principal;

        // lookup our user and external provider info
        // try to determine the unique id of the external user (issued by the provider)
        // the most common claim type for that are the sub claim and the NameIdentifier
        // depending on the external provider, some other claim type might be used
        var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
                          externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
                          throw new Exception("Unknown userid");

        var provider = result.Properties.Items[".AuthScheme"];
        var providerUserId = userIdClaim.Value;

        // find external user
        /*var user = _users.FindByExternalProvider(provider, providerUserId);
        if (user == null)
        {
            // this might be where you might initiate a custom workflow for user registration
            // in this sample we don't show how that would be done, as our sample implementation
            // simply auto-provisions new external user
            //
            // remove the user id claim so we don't include it as an extra claim if/when we provision the user
            var claims = externalUser.Claims.ToList();
            claims.Remove(userIdClaim);
            user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList());
        }*/
        var user = new TestUser()
        {
            SubjectId = "1",
            Username = "Test",
            Password = "Test",
            IsActive = true,
            Claims = new List<Claim>()
            {
                new(ClaimTypes.GivenName, "Test"),
                new(ClaimTypes.Name, "Test"),
                new(ClaimTypes.Role, "Test"),
                new(ClaimTypes.GroupSid, "main"),
                new(ClaimTypes.Sid, "Test"),
                new(IdentityConstants.AdminAccountName, "Test"),
                new(HttpContextConstants.IsTestEnvironmentKey, "true"),
                new(JwtClaimTypes.Subject, "1")
            }
        };

        // this allows us to collect any additional claims or properties
        // for the specific protocols used and store them in the local auth cookie.
        // this is typically used to store data needed for signout from those protocols.
        var additionalLocalClaims = new List<Claim>
        {
            new(JwtClaimTypes.Subject, providerUserId)
        };
        var localSignInProps = new AuthenticationProperties();
        CaptureExternalLoginContext(result, additionalLocalClaims, localSignInProps);

        // issue authentication cookie for user
        var isuser = new IdentityServerUser(user.SubjectId)
        {
            DisplayName = user.Username,
            IdentityProvider = provider,
            AdditionalClaims = additionalLocalClaims
        };

        await HttpContext.SignInAsync(isuser, localSignInProps);

        // delete temporary cookie used during external authentication
        await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);

        // retrieve return URL
        //var token = localSignInProps.GetTokenValue("id_token") ?? "";
        if (!result.Properties.Items.TryGetValue("returnUrl", out var returnUrl))
        {
            returnUrl = $"https://localhost:44377/";
        }

        // check if external login is in the context of an OIDC request
        var context = await interaction.GetAuthorizationContextAsync(returnUrl);

        await events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));

        /*if (context != null)
        {
            if (context.IsNativeClient())
            {
                // The client is native, so this change in how to
                // return the response is for better UX for the end user.
                return this.LoadingPage(returnUrl);
            }
        }*/
        var token = new Token(IdentityServerConstants.TokenTypes.IdentityToken);
        //var userCredential = new UserCredential(user);

        token.Claims = user.Claims;
        token.AccessTokenType = AccessTokenType.Reference;
        token.ClientId = "wiser";
        token.CreationTime = DateTime.UtcNow;
        token.Audiences = new[] {"api1"};
        token.Lifetime = 3600;

        //token.CreateJwtPayload()
        return Redirect(returnUrl);
    }
}

ps。我正在谈论的应用程序实际上是开源的,因此如果有帮助的话我可以发送该应用程序的 GitHub URL。

authentication .net-core google-oauth identityserver4
1个回答
0
投票

你对这个话题有进一步的了解吗,我目前也在研究这个问题

© www.soinside.com 2019 - 2024. All rights reserved.