【问题标题】:How to make a REST Api call to Microsoft graph using MVC C#?如何使用 MVC C# 对 Microsoft 图形进行 REST Api 调用?
【发布时间】:2018-06-01 09:32:14
【问题描述】:

我试图对 Microsoft graph 进行 REST Api 调用以获取我的组织用户。我能够使用邮递员成功拨打电话。但是,我无法使用 c# 代码进行同样的成功调用。在调查使用 JWT 解码器时,我得到的令牌与我使用邮递员得到的不同。我肯定错过了什么。我正在使用 MVC 5 和 .Net 4.6

    public static async Task<AuthenticationResult> GetGraphAccessTokenAsync(string tenant, string clientId, string clientKey)
    {
        var authority = string.Format("https://login.microsoftonline.com/{0}", tenant);

        var resource = "https://graph.microsoft.com";

        AuthenticationContext authenticationContext = new AuthenticationContext(authority);
        var clientCredential = new ClientCredential(clientId, clientKey);
        var result = await authenticationContext.AcquireTokenAsync(resource, clientCredential);

        return result;
    }

【问题讨论】:

  • 你能告诉我令牌之间的区别是什么吗?您可以将其发布在此问题中。
  • 我使用邮递员获得的那个拥有我所有的凭据和范围(如“User.Read”),但是,我通过我的 c# 代码获得的那个没有我的任何凭据它只有应用程序名称、应用程序 ID 和一些其他信息。我想对访问该应用的任何人进行身份验证,并向 AAD 身份验证用户授予访问权限
  • 嗨@Mike,您是说您通过C# 代码获得的令牌没有rolesaio 声明吗?
  • 它确实有 'aio' 声明,但没有 'scp' 和我的凭据(如我的 givenName、电子邮件...)。
  • 当您使用客户端凭据流通过 v1 端点获取 Microsoft graph 的令牌时,访问令牌将不包含您的凭据。您是否使用 Postman 通过client_credentials flow 获取令牌?

标签: c# azure oauth-2.0 azure-active-directory microsoft-graph-api


【解决方案1】:

原因:

获取token的流程不同,结果会有所不同。对于授权码流,它将获得包含 upn 和 scope 的 Delegated access token。对于客户端凭据流,它将获得一个基本(来自应用程序权限)访问令牌。

当您使用授权码流程时:您将获得访问令牌和 id 令牌,其中还包含用户的 upn 和范围,如下所示:

    {
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
  "iat": 1521565239,
  "nbf": 1521565239,
  "exp": 1521569139,
  "app_displayname": "MicrosoftGraphClient",
  "appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
  "family_name": "Yang",
  "given_name": "Wayne",
  "name": "Wayne Yang",
  "unique_name": "wayneyang@contoso.onmicrosoft.com",
  ...
  "tid": "f62472de-8358-4507-aaf3-6a52320f641c",
}

当您使用客户端凭据流时:您将获得没有 usre 的 upn 和范围的访问令牌,如下所示:

    {
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
  "iat": 1521555934,
  "nbf": 1521555934,
  "exp": 1521559834,
  "app_displayname": "MicrosoftGraphClient",
  "appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
  "roles": [
    "Directory.Read.All",
    "User.Read.All",
    ...
    "Mail.ReadWrite",
  ],
  "tid": "f62471de-8358-4907-aaf3-6a52320f741c",
}

解决方案:

您可以在代码中使用授权代码授予流程。但是,由于您想调用 microsoft graph API,我建议您将 MSAL 与 v2 端点一起使用,而不是 ADAL。因为如果你使用 ADAL ,可能会导致一些问题,比如凭证缓存被清除..

.NET 4.6 MVC 应用,可以参考this sample。此示例使用 UseOpenIdConnectAuthentication 和授权码授予流程:

public partial class Startup
    {

        // The appId is used by the application to uniquely identify itself to Azure AD.
        // The appSecret is the application's password.
        // The redirectUri is where users are redirected after sign in and consent.
        // The graphScopes are the Microsoft Graph permission scopes that are used by this sample: User.Read Mail.Send
        private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
        private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
        private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
        private static string graphScopes = ConfigurationManager.AppSettings["ida:GraphScopes"];

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

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {

                    // The `Authority` represents the Microsoft v2.0 authentication and authorization service.
                    // The `Scope` describes the permissions that your app will need. See https://azure.microsoft.com/documentation/articles/active-directory-v2-scopes/                    
                    ClientId = appId,
                    Authority = "https://login.microsoftonline.com/common/v2.0",
                    PostLogoutRedirectUri = redirectUri,
                    RedirectUri = redirectUri,
                    Scope = "openid email profile offline_access " + graphScopes,
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false,
                        // In a real application you would use IssuerValidator for additional checks, 
                        // like making sure the user's organization has signed up for your app.
                        //     IssuerValidator = (issuer, token, tvp) =>
                        //     {
                        //         if (MyCustomTenantValidation(issuer)) 
                        //             return issuer;
                        //         else
                        //             throw new SecurityTokenInvalidIssuerException("Invalid issuer");
                        //     },
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthorizationCodeReceived = async (context) =>
                        {
                            var code = context.Code;
                            string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                            TokenCache userTokenCache = new SessionTokenCache(signedInUserID,
                                context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
                            ConfidentialClientApplication cca = new ConfidentialClientApplication(
                                appId,
                                redirectUri,
                                new ClientCredential(appSecret),
                                userTokenCache,
                                null);
                            string[] scopes = graphScopes.Split(new char[] { ' ' });

                            AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, scopes);
                        },
                        AuthenticationFailed = (context) =>
                        {
                            context.HandleResponse();
                            context.Response.Redirect("/Error?message=" + context.Exception.Message);
                            return Task.FromResult(0);
                        }
                    }
                });
        }
    }

另外,您可以参考this official documentation 来实现您的方案。

【讨论】:

  • 嗨@Mike,这个答案有用吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-10
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多