【问题标题】:Azure B2C - Use msal to get authorization to call Graph APIAzure B2C - 使用 msal 获取调用 Graph API 的授权
【发布时间】:2018-01-30 10:49:39
【问题描述】:

我最近开始使用 B2C。我设法使用 MSAL 和与我自己的租户一起工作的 API 获得了他们的 sample application/API

现在我想:

  1. 了解如何在不使用 API 的情况下运行此示例。该示例使用 Scopes 获得对 API 的读/写访问权限。如果我从应用程序中删除对 API 的引用,它将不再起作用。当然应该有某种方法可以在不需要 API 的情况下对 B2C 进行身份验证?这对我的应用程序来说并不重要,但我很好奇 web 服务 HAS 是否作为身份验证过程的一部分存在?
  2. 与 Graph Api(Windows 或 Microsoft Graph?)通信。 sample MS 提供使用 ADAL 和一些控制台应用程序。我找不到使用 MSAL 的示例,因此无法将其合并到我自己的应用程序中。现在是否可以使用 MSAL 调用 Graph API?如果是,是否有一些关于如何在某处执行此操作的文档?

我尝试简单地按照上面的文档并注册一个应用程序/授予它权限。然后将客户端 ID/密钥放入我自己的应用程序(第一个示例中的 MSAL 应用程序),但随后我从 B2C 收到一条消息,如下所示:

相关 ID:01040e7b-846c-4f81-9a0f-ff515fd00398 时间戳:2018-01-30 10:55:37Z AADB2C90068:提供的 ID 为“9cd938c6-d3ed-4146-aee5-a661cd7d984b”的应用程序对该服务无效。请使用通过 B2C 门户创建的应用程序,然后重试。

确实没有通过B2C门户注册,但说明书上是这么说的;在应用注册下的 B2C 租户中注册,而不是在 B2c 门户中注册。

所有魔法发生的 Startup 类如下所示:

public partial class Startup
{

    // App config settings
    public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
    public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
    public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
    public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
    public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
    public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];

    public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
    public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
    public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
    public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };

    // B2C policy identifiers
    public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
    public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
    public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];

    public static string DefaultPolicy = SignUpSignInPolicyId;

    // OWIN auth middleware constants
    public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";

    // Authorities
    public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);

    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
            // Generate the metadata address using the tenant and policy information
            MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),

            // These are standard OpenID Connect parameters, with values pulled from web.config
            ClientId = ClientId,
                RedirectUri = RedirectUri,
                PostLogoutRedirectUri = RedirectUri,

            // Specify the callbacks for each type of notifications
            Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = OnRedirectToIdentityProvider,
                    AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                    AuthenticationFailed = OnAuthenticationFailed,
                },

            // Specify the claims to validate
            TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name"
                },

            // Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
            Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
            }
        );

    }

    /*
     *  On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
     *  If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
     */
    private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        var policy = notification.OwinContext.Get<string>("Policy");

        if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
        {
            notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
            notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
            notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
        }

        return Task.FromResult(0);
    }

    /*
     * Catch any failures received by the authentication middleware and handle appropriately
     */
    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        notification.HandleResponse();

        // Handle the error code that Azure AD B2C throws when trying to reset a password from the login page 
        // because password reset is not supported by a "sign-up or sign-in policy"
        if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
        {
            // If the user clicked the reset password link, redirect to the reset password route
            notification.Response.Redirect("/Account/ResetPassword");
        }
        else if (notification.Exception.Message == "access_denied")
        {
            notification.Response.Redirect("/");
        }
        else
        {
            notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
        }

        return Task.FromResult(0);
    }


    /*
     * Callback function when an authorization code is received 
     */
    private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
    {
        // Extract the code from the response notification
        var code = notification.Code;

        string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
        TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
        ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
        try
        {
            AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
        }
        catch (Exception ex)
        {
            //TODO: Handle
            throw;
        }
    }

}

【问题讨论】:

    标签: azure microsoft-graph-api azure-ad-b2c


    【解决方案1】:

    关于 #2,您只能将 Azure AD Graph API 与 Azure AD B2C 目录一起使用,如 the "Azure AD B2C: Use the Azure AD Graph API" article 中所述。

    这里是如何(我从a previous answer复制的)...

    Azure AD B2C 使用 Azure AD v2.0 端点颁发令牌:

    https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
    https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
    

    Azure AD Graph API 需要使用 Azure AD v1.0 端点颁发的令牌:

    https://login.microsoftonline.com/{tenant}/oauth2/token
    

    在设计时:

    1. 使用 Azure AD B2C 门户注册 Web 应用程序。
    2. Register the web application using the Azure AD portalgrant the Read directory data permission

    在运行时:

    1. Web 应用程序将最终用户重定向到 Azure AD B2C v2.0 终结点以进行登录。 Azure AD B2C 颁发一个包含用户标识符的 ID 令牌。
    2. Web 应用程序使用在设计时在步骤 2 中创建的应用程序凭据从 Azure AD v1.0 端点获取访问令牌。
    3. Web 应用程序调用 Azure AD Graph API,传递在步骤 1 中收到的用户标识符和在步骤 2 中发布的访问令牌,并查询用户对象等。

    【讨论】:

    • 感谢您的帮助。这很复杂。
    • 没错——这并不简单。你想用 Graph API 做什么?为用户读取属性?写出来?
    • 我想获得会员组,不幸的是您无法获得声明中的那些:feedback.azure.com/forums/169401-azure-active-directory/…
    • 正确 -- 因此,在第 4 步,用户 API 可以查询组成员资格,然后将这些返回给单页应用程序。
    • 道歉。这是相似的。我已经用网络应用流程更新了上述答案。
    【解决方案2】:

    这个答案只是解决问题#1,我不确定#2,除了据我所知它没有公开记录。

    B2C 不要求您在构建应用时使用 API/服务。在示例中,您正在查看它使用 2 个库来执行略有不同(但相关)的事情。

    首先,它使用 OWIN 中间件模块来帮助促进身份验证。这有助于您的应用识别用户是谁,但不会授权您的应用代表他们执行操作或访问数据。该库将为您提供与最终用户的会话和有关他们的基本信息以及您以后可以使用的授权代码。

    正在使用的另一个库是 MSAL。 MSAL 是用于令牌获取和管理的客户端库。在这种情况下,在使用上述中间件进行初始身份验证后,MSAL will use the authorization code 获取访问和刷新令牌,您的应用可以使用这些令牌调用 API。这可以在没有最终用户交互的情况下发生,因为他们已经同意该应用程序(并且您已经配置了您的应用程序所需的权限)。然后,MSAL 管理这些令牌的刷新和缓存,并使它们可以通过 AcquireTokenSilent() 访问。

    为了从示例应用中移除 API 功能,您需要做的不仅仅是移除范围。具体来说,删除TaskController.cs 中尝试调用 API 的代码,删除 MSAL 的大部分使用,可能还有更多内容。 This sample 实现了仅 Web 应用程序架构(警告:它适用于 Azure AD 而不是 Azure AD B2C。代码非常相似,但需要进行一些修改)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-03
      • 2019-03-05
      • 1970-01-01
      • 2014-05-22
      • 1970-01-01
      • 2017-01-04
      • 2021-03-29
      • 1970-01-01
      相关资源
      最近更新 更多