6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Microsoft Graph を使ってみよう : Azure AD v2 エンドポイントのアプリケーション認証を MSAL で行う

Last updated at Posted at 2018-05-26

#ユーザー委任とアプリケーション認証

Azure AD v2 エンドポイントの認証方法は 2 つあります。

ユーザーに委任されたアクセス許可
ユーザーが明示的にサインインして、アプリケーションで必要なアクセス許可に同意をする方法。

アプリケーションのアクセス許可
バックグランドサービス用で、事前に管理者が必要なアクセス許可に同意をし、プログラム実行時には認証を聞かれない方法。アプリケーション認証といわれることもある。

アプリケーション認証と Microsoft Authentication Library (MSAL)

今回は以下のアプリケーションを作ります。

  • C# コンソールアプリケーション
  • アプリケーション認証シナリオ
  • MSAL を利用

アプリケーションの登録

OAuth 2.0 を使うために、まず初めにアプリケーションを登録します。

1. Microsoft アプリ登録ポータル にアクセスして、ログイン。ここでは組織アカウントを使用。

Capture.PNG

2. ユーザー委任シナリオで作成したアプリケーションを利用、または新規に登録。プラットフォームの項目で「プラットフォームの追加」をクリック。Web アプリケーションを選択。

Capture.PNG

3. アプリケーションシークレットの項目で「新しいパスワードを生成」をクリック。パスワードが表示されるのでコピーして保存。
※このパスワードは画面を消すと二度と取得できない。

Capture.PNG

4. Microsoft Graph のアクセス許可の「委任されたアクセス許可」項目で、「追加」をクリック。

Sketch.png

5. 必要な権限を追加。ここでは User.ReadWrite.All を選択。

Capture.PNG

6. 最後に「保存」をクリック。

管理者の同意を取得

ユーザーなしでアクセスを行う場合、および権限に管理者の同意が必要な場合は、エンドポイントにリクエストを投げて同意を行います。

1. ブラウザに以下のアドレスを入力。

  • テナント名は自分のものに変更
  • client_id : 登録したアプリケーションのアプリケーション ID
  • redirect_uri : 送信したリクエストの戻り先 (今回は受け取って処理しないので仮のアドレス)
  • state : リクエストの応答に含まれる値で戻り先で検証が可能

https://login.microsoftonline.com/graphdemo01.onmicrosoft.com/adminconsent
?client_id=550dcc01-129d-4b51-b80c-96995a6848e3
&state=12345
&redirect_uri=http://localhost

2. サインイン画面が出るので、管理者権限でサインイン。この場合、アプリケーション権限だけでなく、ユーザーの権限も含めたすべての権限が表示される。よってカレンダーの権限も求められる。

image.png

3. 結果はリダイレクトされるので URL で admin_consent の値が True であることを確認。先ほど指定した redirect_uri にリダイレクトされ、また state も指定した元同じ値が戻る。

コンソールアプリケーションの開発

1. Visual Studio で C# のコンソールアプリケーションプロジェクトを作成。

image.png

2. NuGet の管理で 「Microsoft.Identity.Client (MSAL)」と「JSON.NET」を追加。尚 MSAL はプレビューのため、プレビューを含める。

image.png

3. 参照より System.Security を追加。

image.png

4. TokenCacheHelper.cs ファイルを追加して、以下のコードで差し替え。

TokenCacheHelper.cs
using Microsoft.Identity.Client;
using System.IO;
using System.Security.Cryptography;

namespace GraphAppDemo
{
    public partial class Program
    {
        static class TokenCacheHelper
        {
            /// <summary>
            /// Get the user token cache
            /// </summary>
            /// <returns></returns>
            public static TokenCache GetAppCache()
            {
                if (appTokenCache == null)
                {
                    appTokenCache = new TokenCache();
                    appTokenCache.SetBeforeAccess(BeforeAccessNotification);
                    appTokenCache.SetAfterAccess(AfterAccessNotification);
                }
                return appTokenCache;
            }
            
            static TokenCache appTokenCache;
         
            /// <summary>
            /// Path to the token cache
            /// </summary>
            public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin";

            private static readonly object FileLock = new object();

            public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
            {
                lock (FileLock)
                {
                    args.TokenCache.Deserialize(File.Exists(CacheFilePath)
                        ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
                                                    null,
                                                    DataProtectionScope.CurrentUser)
                        : null);
                }
            }

            public static void AfterAccessNotification(TokenCacheNotificationArgs args)
            {
                // if the access operation resulted in a cache update
                if (args.TokenCache.HasStateChanged)
                {
                    lock (FileLock)
                    {
                        // reflect changesgs in the persistent store
                        File.WriteAllBytes(CacheFilePath,
                                            ProtectedData.Protect(args.TokenCache.Serialize(),
                                                                    null,
                                                                    DataProtectionScope.CurrentUser)
                                            );
                        // once the write operationtakes place restore the HasStateChanged bit to filse
                        args.TokenCache.HasStateChanged = false;
                    }
                }
            }
        }
    }
}

5. Program.cs を以下のコードと差し替え。テナント名など適宜書き換え。

C#
using Microsoft.Identity.Client;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace GraphAppDemo
{
    public partial class Program
    {
        private static string clientId = "550dcc01-129d-4b51-b80c-96995a6848e3";
        private static string authority = "https://login.microsoftonline.com/graphdemo01.onmicrosoft.com/v2.0";
        private static string redirectUri = "http://localhost";
        private static Uri graphUrl = new Uri("https://graph.microsoft.com/");
        private static string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
        private static string secret = "hSL3....";
        private static ConfidentialClientApplication cca;
   
        static void Main(string[] args)
        {
            cca = new ConfidentialClientApplication(clientId, authority,
                redirectUri, new ClientCredential(secret), null, TokenCacheHelper.GetAppCache());
            
            Program p = new Program();
            p.Run().Wait();
            Console.ReadLine();
        }

        private async Task Run()
        {
            using (HttpClient client = new HttpClient())
            {
                string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
                client.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", await GetAccessTokenAsync(scopes));
                client.BaseAddress = graphUrl;

                var result = await client.GetAsync("/v1.0/users");
                if (result.IsSuccessStatusCode)
                    Console.WriteLine(JsonConvert.SerializeObject(
                        JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()),
                        Formatting.Indented));
                
            }
        }

        private async Task<string> GetAccessTokenAsync(string[] scopes)
        {
            AuthenticationResult authResult = null;

            authResult = await cca.AcquireTokenForClientAsync(scopes);

            if (authResult != null)
                return authResult.AccessToken;
            else
                return null;
        }
    }
}

6. F5 キーを押下してプログラムを実行。

image.png

コードの詳細確認

ではコードの詳細を確認していきます。

スコープ
アプリケーション認証の場合のスコープは、ユーザー認証と異なり、https://graph.microsoft.com/.default 1 つだけとなります。これで登録したすべての権限を要求します。

認証
まず MSAL クライアントを作成。アプリケーション認証をする場合は、ConfidentialClientApplication を利用します。

引数として 

  • clientId : 登録したアプリケーション ID
  • authority : ログイン先テナントを指定した login.microsoftonline.com アドレス
  • redirectUri : Web プラットフォームに登録したリダイレクト URL
  • ClientCredential : 取得したパスワードを指定して作成
  • UserTokenCache/AppTokenCache: ユーザートークンおよびアプリケーショントークンのキャッシュ
C#
authClient = new ConfidentialClientApplication(clientId, authority, redirectUri, new ClientCredential(secret), null, TokenCacheHelper.GetAppCache());

次にアクセストークン取得です。アプリケーション認証のトークンは、AcquireTokenForClientAsync メソッドにスコープのみ指定して実行。

C#
authClient.AcquireTokenForClientAsync(scopes)

Microsoft Graph の呼び出し
HttpClient を使って Graph を呼び出しています。Graph の C# SDK はまた別の機会に紹介します。

タイトルを更新

権限も付与済ですので、ユーザーのタイトルを更新してみます。

1. Run メソッドを以下のコードと差し替え。

C#
private async Task Run()
{
    using (HttpClient client = new HttpClient())
    {
        string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", await GetAccessTokenAsync(scopes));
        client.BaseAddress = graphUrl;

        var result = await client.GetAsync("/v1.0/users?$select=id,displayName,jobTitle");
        var users = JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync());


        foreach (var user in (users as JToken)["value"] as JArray)
        {
            var id = user.Value<string>("id");
            user["jobTitle"] = "アーキテクト";
            var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"/v1.0/users/{id}");
            request.Content = new StringContent(JsonConvert.SerializeObject(user), System.Text.Encoding.UTF8, "application/json");
            result = await client.SendAsync(request);
        }

        result = await client.GetAsync("/v1.0/users?$select=id,displayName,jobTitle");
        Console.WriteLine(JsonConvert.SerializeObject(
                JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()),
                Formatting.Indented));
    }
}

2. F5 キーを押下して実行。結果を確認。

image.png

まとめ

アプリケーション認証は管理者同意が必要な強力な権限を必要としますが、バックグランドサービスとして動かしておくシナリオでは必須です。

目次へ戻る

参照

ユーザーなしでアクセスを取得
Azure Active Directory v2.0 と OAuth 2.0 クライアント資格情報フロー
Microsoft Graph Client Library for .NET

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?