如何pass/verify 在.net core web app 和web api 之间打开ID token?

How to pass/verify Open ID token between .net core web app and web api?

我们有以下架构:

我已经在 Web 应用程序中实现了 Okta 小部件和中间件。用户可以登录,之后我就可以获得 ClaimsPrincipal,访问他们的所有范围,并获取我通过 open id 存储的任何自定义配置文件数据。我可以通过 [Authorize] 装饰获得视图。一切正常。

我现在需要做的是在 API 端实施安全检查。我花了很多时间查看示例并发现了很多,但我要么遗漏了一些明显的东西,要么我所做的是独一无二的(我无法想象我所做的是那么独特)。基本上我需要做的是:

这样我就可以在 API 端实施必要的安全逻辑。假设 return 的客户订单是 API - 我需要确保调用它的用户是管理员或实际客户(所以我不 return客户数据给不应该看到的人)。我已经弄清楚了所有角色的东西,我就是不能,为了我的生活,弄清楚如何通过令牌确定某人是谁?

传递令牌非常简单,但如何从 ClaimsPrincipal 对象中获取令牌?或者我是否需要在用户登录后调用 Okta API 来专门获取访问和 ID 令牌?

当然,我必须弄清楚如何让 API 端正确验证和解析发送的令牌。

如果任何人都可以帮助我开始这个或为我指出正确的方向作为例子,我将非常感激。至此,我已经阅读了我能找到的关于 Owin、OpenID、Okta、.net core 授权的所有文章。

您的 ID 提供者,在本例中为 Okta,将颁发一个 OpenID Connect 授权持有者令牌,您需要将该令牌传递给您想要保护的任何应用程序。

在应用程序的 Web Api 端,您需要注册中间件来处理 Okta 的 OpenID Connect 令牌。然后你可以用 [Authorize] 装饰你的 controllers/actions 并且你可以检查身份声明。

多亏了 Cameron Tinker 的建议,我才得以完成这项工作。有几件事让我很困惑,所以我会在这里分享它们,以防有人有同样的经历。

如果您使用的是 Okta,则可以通过 Okta 中间件包完成所有这些操作。您可以只使用 c# OpenID 库来完成,但 Okta.AspNetCore 库会有所帮助。

首先在网络应用中注册中间件。 Okta 在他们的网站上有很多这样的例子,而且非常简单。

在您的网络应用程序中,您可以使用它来获取令牌(当然是在用户通过身份验证之后)

await context.HttpContext?.GetTokenAsync("id_token")

通过标准机制将其作为 header 的一部分在您的 API 调用中发送:

"Authorization" : "Bearer [token]"

在 Web API 端,您使用相同的 Okta.AspNetCore 中间件包,然后可以使用 [Authorize] 装饰您的控制器以对它们强制执行身份验证。这是我被绊倒的地方。如果您没有使用 Okta 中的默认身份验证服务器并为您的应用程序设置了自定义服务器,则需要在配置中指定它和受众:

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
                options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
                options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
            })
            .AddOktaWebApi(new OktaWebApiOptions()
            {
                OktaDomain = oktaDomain,
                AuthorizationServerId = authServerId,
                Audience = clientId
            });

            services.AddAuthorization();

我完全忘记了观众部分——根据令牌验证的工作方式,这部分是必需的。

从那里开始,中间件负责为您填充 ClaimsPrincipal,因此您可以通过 ClaimsPrincipal (HttpContext.User) 访问用户信息。我最终创建了一个 "CurrentUserService" 并将其拉出到它自己的库中,这样我就可以在那里整合我所有的身份验证处理程序;从而允许我的网络应用程序和网络 api 代码以相同的方式检查权限并检索有关当前用户的信息。如果您有兴趣,代码在这里:

    public interface ICurrentUserService
    {
        public ClaimsPrincipal GetCurrentUser();

        public string GetCurrentUserDisplayName();

        public string GetCurrentUserFullName();

        public string GetCurrentUserId();

        public DateTime? GetCurrentUserDob();

        public string GetCurrentUserGender();

        public AddressFromClaimsDTO GetCurentUserAddress();

        public bool IsAuthenticated();
    }

    public class CurrentUserService : ICurrentUserService
    {

        private const string FULL_ADDRESS_CLAIM_TYPE = "address";

        private readonly IHttpContextAccessor _context;

        public CurrentUserService(IHttpContextAccessor context)
        {
            _context = context;
        }

        /// <summary>
        /// Gets whether or not the current user context is authenticated.
        /// </summary>
        /// <returns></returns>
        public bool IsAuthenticated()
        {
            return GetCurrentUser().Identity.IsAuthenticated;
        }

        /// <summary>
        /// Gets the current user's address.
        /// TODO: tie this into our address data model... but if addresses live in Okta what does that mean?
        /// </summary>
        /// <returns></returns>
        public AddressFromClaimsDTO GetCurentUserAddress()
        {
            var addressClaim = GetClaim(FULL_ADDRESS_CLAIM_TYPE);

            if (addressClaim != null)
            {
                //var parseValue = addressClaim.Value.ToString().Replace("{address:", "{\"address\":");
                var address = JsonSerializer.Deserialize<AddressFromClaimsDTO>(addressClaim.Value.ToString());
                return address;
            }
            else
            {
                return new AddressFromClaimsDTO();
            }
        }

        public ClaimsPrincipal GetCurrentUser()
        {
            return _context.HttpContext.User;
        }

        public string GetCurrentUserDisplayName()
        {
            return GetCurrentUser().Identity.Name;
        }

        public string GetCurrentUserFullName()
        {
            throw new NotImplementedException();
        }

        public string GetCurrentUserId()
        {
            throw new NotImplementedException();
        }

        public DateTime? GetCurrentUserDob()
        {
            var claim = GetClaim("birthdate");

            if (claim != null && !string.IsNullOrEmpty(claim.Value))
            {
                return DateTime.Parse(claim.Value);
            }
            else
            {
                return null;
            }
        }

        public string GetCurrentUserGender()
        {
            return GetClaim("gender")?.Value.ToString();
        }


        public Claim GetClaim(string claimType)
        {
            return _context.HttpContext.User.FindFirst(x => x.Type == claimType);
        }

    }