web-dev-qa-db-ja.com

AuthenticationContextでUserPasswordCredentialを使用して認証するときにGraphServiceClientを使用して更新トークンを取得する

私が何時間も読んだ後、私は私の知恵の終わりにいるので、この投稿から何かを見逃した場合は心からお詫びします。

Azure AD経由でMS Graph APIに接続するバックエンドサービス(Windows)を記述しようとしています。私はC#を使用して概念実証を行っていますが、MSのドキュメント、ブログなどがかなり複雑で品質が低いため、多くの問題が発生しています。彼らはすべて、APIの利用者がフロントエンドのデスクトップまたはブラウザーベースのアプリであると想定しているようです。

とにかく、サービスアカウントのUserPasswordCredentialを使用してなんとかグラフに接続できたので、AuthenticationContext応答でトークンを受け取りましたが、更新トークン情報がありません。 MSDNは、メソッドmayが更新情報を返すことを推奨していますが、そうではない場合があります。

さらに、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を使用して新しいトークンを取得できるため、更新や再認証などのスケジュールを処理する必要はないようです。クライアントの取得の一部であるデリゲートハンドラー(呼び出すメソッドについて)。

これが私がテストしたサンプルコードの更新されたバージョンで、動作するようです。

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記事が与えられました。

認証シナリオ

Azure AD V2.0エンドポイント-クライアント資格情報フロー

これが以前と同じように、髪を抜いていたのと同じように、私と同じ状況にいる他の人を助けることを願っています!

6
peteski

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

9
Fei Xue - MSFT