在AuthenticationContext

时间:2017-05-16 17:15:36

标签: c# azure authentication oauth-2.0 microsoft-graph

如果我错过了这篇文章中的某些内容,真诚的道歉,因为我在读了好几个小时之后我的智慧结束了。

我正在尝试编写一个后端服务(Windows),它将通过Azure AD连接到MS Graph API。我正在使用C#来破坏概念验证,但是在MS文档,博客等方面遇到了很多问题,这些问题相当令人费解并且质量很差。他们似乎都认为他们的API的消费者是前端桌面或基于浏览器的应用程序。

无论如何,我设法使用服务帐户的UserPasswordCredential连接到Graph,我在AuthenticationContext响应中收到一个令牌,但没有刷新令牌信息。 MSDN建议方法可以返回刷新信息,但是嘿,它可能不会。

此外,阅读(或尝试阅读)ADAL 3上的博客文章(自2015年起):

http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/

让我对刷新现在如何运作感到困惑。这篇文章似乎暗示着令人神奇的是令牌在缓存中为你刷新,但在用我的POC测试之后情况并非如此。

我也从MS那里看到了这篇文章,似乎在标题中指出:

https://msdn.microsoft.com/en-us/office/office365/howto/building-service-apps-in-office-365

然而,需要你注册应用程序,跳过允许访问等的弹出式箍等等。

由于我的示例正在运行,我已经尝试安排重新验证,以便在获得初始令牌(持续60分钟)后50分钟获得新令牌但是我得到相同的令牌。这意味着在60分钟1秒内,通过客户端的任何调用都会抛出异常(ServiceException,我必须检查里面的文本以查看令牌到期相关信息)。对我来说,无法重新验证和刷新令牌以继续使用客户端进行更“无缝”的使用是没有意义的。

以下是我的代码的精简版示例:

namespace O365GraphTest
{
    using Microsoft.Graph;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Nito.AsyncEx;
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using System.Linq;
    using System.Threading;
    using System.Security;

    public class Program
    {
        // Just use a single HttpClient under the hood so we don't hit any socket limits.
        private static readonly HttpProvider HttpProvider = new HttpProvider(new HttpClientHandler(), false);

        public static void Main(string[] args)
        {
            try
            {
                AsyncContext.Run(() => MainAsync(args));
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private static async Task MainAsync(string[] args)
        {
            var tenant = "mytenant.onmicrosoft.com";
            var username = $"test.user@{tenant}";
            var password = "fooooooo";
            var token = await GetAccessToken(username, password, tenant);
            var client = GetClient(token)

            // Example of graph call            
            var skusResult = await client.SubscribedSkus.Request().GetAsync();
        }

        private static async Task<string> GetAccessToken(string username, string password, string tenant = null)
        {
            var authString = tenant == null ?
                $"https://login.microsoftonline.com/common/oauth2/token" :
                $"https://login.microsoftonline.com/{tenant}/oauth2/token";

            var authContext = new AuthenticationContext(authString);
            var creds = new UserPasswordCredential(username, password);
            // Generic client ID
            var clientId = "1950a258-227b-4e31-a9cf-717495945fc2";
            var resource = "https://graph.microsoft.com";

            // NOTE: There's no refresh information here, and re-authing for a token pre-expiry doesn't give a new token.
            var authenticationResult = await authContext.AcquireTokenAsync(resource, clientId, creds);

            return authenticationResult.AccessToken;
        }

        private static GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
        {
            var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
            {
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);

                return Task.FromResult(0);
            });

            var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? HttpProvider);

            return graphClient;
        }
    }
}

如果有人可以帮助我,而不仅仅是说“以不同的方式做”,我将非常感激,因为似乎很少有关于这个在线讨论(Freenode,IRC)和博客是旧的,文档在MSDN简洁和库的特定先前版本等。例如

Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireTokenAsync

现在已删除了AuthenticationContext类的刷新方法。

感谢您的任何建议。

彼得

更新

首先感谢@Fei Xue回答我的问题,但是我无法理解他们开始说的话。

与Fei Xue聊天后,似乎如果你保持身份验证上下文,你可以使用AcquireTokenSilentAsync来获取新的令牌,你不需要处理任何刷新/ reauth等的安排。只需烘烤一个检查委托处理程序(关于你正在调用的方法),这是获取客户端的一部分。

这是我测试过的示例代码的更新版本,似乎有效。

namespace O365GraphTest
{
    using Microsoft.Graph;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Nito.AsyncEx;
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;

    public class Program
    {
        private const string Resource = "https://graph.microsoft.com";

        // Well known ClientID
        private const string ClientId = "1950a258-227b-4e31-a9cf-717495945fc2";

        private static readonly string Tenant = "mytenant.onmicrosoft.com";

        private static readonly HttpProvider HttpProvider = new HttpProvider(new HttpClientHandler(), false);

        private static readonly AuthenticationContext AuthContext = GetAuthenticationContext(Tenant);

        public static void Main(string[] args)
        {
            try
            {
                AsyncContext.Run(() => MainAsync(args));
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private static async Task MainAsync(string[] args)
        {
            var userName = $"test.user@{Tenant}";
            var password = "fooooooo";
            var cred = new UserPasswordCredential(userName, password);

            // Get the client and make some graph calls with token expiring delays in between.        
            var client = GetGraphClient(cred);
            var skusResult = await client.SubscribedSkus.Request().GetAsync();
            await Task.Delay(TimeSpan.FromMinutes(65));
            var usersResult = await client.Users.Request().GetAsync();
        }

        private static AuthenticationContext GetAuthenticationContext(string tenant = null)
        {
            var authString = tenant == null ?
                $"https://login.microsoftonline.com/common/oauth2/token" :
                $"https://login.microsoftonline.com/{tenant}/oauth2/token";

            return new AuthenticationContext(authString);
        }

        private static GraphServiceClient GetGraphClient(UserPasswordCredential credential)
        {
            var delegateAuthProvider = new DelegateAuthenticationProvider(async (requestMessage) =>
            {
                var result = AuthContext.TokenCache?.Count > 0 ?
                    await AuthContext.AcquireTokenSilentAsync(Resource, ClientId) :
                    await AuthContext.AcquireTokenAsync(Resource, ClientId, credential);
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
            });

            return new GraphServiceClient(delegateAuthProvider, HttpProvider);
        }
    }
}

我还获得2篇MS文章参考:

Authentication scenarios

Azure AD V2.0 endpoint - client credentials flow

我希望这可以帮助处于同样情况的其他人,就像以前一样,我把头发拉出来了!

1 个答案:

答案 0 :(得分:5)

GraphServiceClient类用于操作无法获取access_token或refresh_token的Microsoft Graph。

正如博客提到的azure-activedirectory-library-for-dotnet库的最新版本没有将refresh_token暴露给开发人员。您可以从AuthenticationResult.cs课程查看。当我们调用方法AcquireTokenSilentAsync时,如果令牌过期,此库将帮助刷新access_token。

因此,在您的方案中,我们应该使用此方法来获取GraphServiceClient的访问令牌。然后它将始终为GraphServiceClient提供可用的访问令牌。以下是供您参考的代码:

string authority = "https://login.microsoftonline.com/{tenant}";
string resource = "https://graph.microsoft.com";
string clientId = "";
string userName = "";
string password = "";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resource, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
    new DelegateAuthenticationProvider(
        (requestMessage) =>
        {
            var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
            return Task.FromResult(0);
        }));

var a = graphserviceClient.Me.Request().GetAsync().Result;

更新

string authority = "https://login.microsoftonline.com/adfei.onmicrosoft.com";
string resrouce = "https://graph.microsoft.com";
string clientId = "";
string userName = "";
string password = "";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resrouce, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
    new DelegateAuthenticationProvider(
        (requestMessage) =>
        {
            var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
            return Task.FromResult(0);
        }));

var a = graphserviceClient.Me.Request().GetAsync().Result;

//simulate the access_token expired to change the access_token
graphserviceClient.AuthenticationProvider=
     new DelegateAuthenticationProvider(
         (requestMessage) =>
         {
             var access_token = "abc";
             requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
             return Task.FromResult(0);
         });
var b = graphserviceClient.Me.Request().GetAsync().Result;

结果: enter image description here