AADアプリケーションと認可サーバーを理解する（その１・・・の予定）


#はじめに

Microsoft Azure Active Directory (AAD)を利用してコンソールアプリケーション、Single page app（SPA）、そしてAPIなどへのユーザーの認証や認可などが出来ますが、いろんな技術がてんこ盛りで、さらにいろんなシナリオがサポートされていて、訳が分からなくなります。

関連する技術や用語は、思いつくだけでも、Open ID Connect、OAuth2、JWT、Bearer token、IDトークン、アクセストークン、認証、認可、署名、AADアプリケーション登録、AADテナント、アプリケーションID、クライエントアプリケーション、認可サーバ、リソースサーバ、GrantにFlow、などなどです。

これらの技術と概念を急いで理解しようとしても、あっさりとカオスとストレスに埋もれてしまいます。抜け出すには、時間をかけて、Divide and Conquer (分割し統治する）しかありません。

##この記事で作成するAADアプリケーション

この記事では、とりあえず以下のような条件の、出来るだけ簡単なアプリケーションを作ります。

- 自分のローカルのマシーンで実行できるコンソールアプリケーション（.NET Framework向け。実行環境はWindowsプラットフォームに限定）
- コンソールアプリケーションには、Microsoft Graph APIというサービスの利用許可を与えられるように設定

> 全く同じコンソールアプリケーションを、.NET Core向け（クロスプラットフォーム対応。Windows環境、Linux環境、もしくはMac環境で実行可能）にビルドできるプロジェクトを別記事で解説しています。ユーザー体験が異なるので別アングルから同じコンソールアプリケーションを考察して理解を深めてください。

## .NET Framework向けアプリケーションのプロジェクトを作成

ここでは Windows環境のみで実行することを目的にしたコンソールアプリケーションを、.NET Coreのプロジェクトとして作成します。

> 注：.NET Core のプロジェクトで、従来通りのWindows環境で実行するプログラムを作成することは可能です。下の`csproj`ファイルの`<TargetFramework>`タグの値が`net47`に指定されていることに留意してください。

作成する .NET Core プロジェクトはプロジェクトファイル（`csproj`拡張子）とメインのプログラムファイル（`.cs`拡張子）の二つだけです。

.NET Core プロジェクトは適当に空のディレクトリに以下の２つのファイルを作成して、VSCodeで開いて開発環境とするか、Visual Studioで新規.NET Core コンソールアプリのプロジェクトを作成するか、どちらか好きな方で。

```xml:aad-client-console.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net47</TargetFramework>
    <RootNamespace>aad_client_console</RootNamespace>
  </PropertyGroup>

    <ItemGroup>
    <PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="[3.19.3,4)" />
    <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
    <PackageReference Include="System.Net.Http" Version="4.3.3" />
  </ItemGroup>

</Project>
```

続いてメインとなるプログラムのコード。

```c#:Program.cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace aad_client_console
{
    class Program
    {
        static void Main(string[] args)
        {
            var authContext = new AuthenticationContext("TODO_AUTHORITY_URL");

            var authResult = authContext.AcquireTokenAsync(
                    "TODO_RESOURCE",
                    "TODO_APPLICATION_ID",
                    new Uri("TODO_REDIRECT_URL"),
                    new PlatformParameters(PromptBehavior.Always)).Result;

            Console.WriteLine("ID token: " + authResult.IdToken);
            Console.WriteLine("Access token: " + authResult.AccessToken);
        }
    }
}
```

ロジックはこうです

1. AuthenticationContextというオブジェクトを作ってAAD認証/認可サーバーを表す
2. AAD認証/認可サーバーに「トークンを発行してください」とお願いする
3. 発行された２つのトークン（IDトークンとアクセストークン）の内容を出力する

> 「AAD認証/認可サーバー」という呼び方は面倒だし一般的でないので今後は「AAD認可サーバー」と記します。認証と認可のサービスの境界線が実際には曖昧なことも混乱の原因なんですが、これはちょっと仕方ない状況なんで。そのうちなんとなく分かりますし、使えるレベルの理解には到達できます。

お気づきでしょうが、上の`Program.cs`には４つのTODOアイテムを設けています。

- TODO_AUTHORITY_URL セキュリティートークンを発行してもらうサーバー
- TODO_RESOURCE　アクセスしたいリソース
- TODO_APPLICATION_ID　コンソールアプリケーションを表すID
- TODO_REDIRECT_URL　コンソールアプリケーションをAADに登録した時に指定するURL

これらのTODOアイテムに、ご自分の環境の値を代入することになります。以下、その値を得るためのステップを解説します。

##Azure Active Directory にコンソールアプリケーションを登録する

まずはAzure Active Directoryに、このコンソールアプリケーションを表すアプリケーション・オブジェクトを登録していきましょう。

アプリケーションの名前に「my-aad-client-console」を、アプリケーションタイプには「Native」を指定しました。Redirect URLの項目には「テナント名＋アプリケーションの名前」を合わせた`https://あなたのテナント名.onmicrosoft.com/my-aad-client-console`というようなURLを指定しました（このURLに実際にサービスエンドポイントなどがマップされている必要はありません。）

<img src="https://qiita-image-store.s3.amazonaws.com/0/103139/bfc2d59b-0f5e-3e01-e7ea-beebbad0ce7d.png" width=60%>

登録すると、AADのアプリケーション・オブジェクトという、アプリケーションを表すオブジェクトが作成されます。

>「AADアプリケーション・オブジェクト」と「アプリケーション」の関係は、「リファレンス」と「クラスのインスタンス」に似ています。どちらも単純に「アプリケーション」と呼ぶこともあって、それも混乱の原因です。厳密には「AADアプリケーション・オブジェクト」は、独自の設定内容を持つ、別のデータです（その証拠にAADアプリケーション・オブジェクトを削除しても、アプリケーションそのものは存在し続けますよね）。

### TODO_REDIRECT_URLの値

この段階ですでに`TODO_REDIRECT_URL`の値が分かりました。アプリケーションを登録した時のURL（`https://あなたのテナント名.onmicrosoft.com/my-aad-client-console`）を、TODO_REDIRECT_URLと差し替えましょう。

### TODO_APPLICATION_IDの値

アプリケーションの登録が完了した時、`TODO_APPLICATION_ID`に使うGUID型の値も自動的に生成されました。私の場合は`b0905193...`で始まる値でした。ここでも、TODO_APPLICATION_IDの箇所にこの値を代入します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/103139/3fd21745-0b8d-ac43-0c4d-c91280fc48cc.png" width=80%>


残るは`TODO_AUTHORITY_URL`と`TODO_RESOURCE`の二つですが、どちらもすでにある情報から値がわかります。以下、ひとつずつ見ていきましょう。

### TODO_AUTHORITY_URLの値
Authority（権威？）は、セキュリティートークンを発行してもらうSecurity Token Service (STS)もしくは authorization server （認可サーバー）のことで、Microsoft AzureではAzure Active Directoryがこれらのサービスを実装しています。Authority URLはこのサーバーを指し示すコネクションストリングと理解すればよいでしょう。

このURLの背後にあるサーバーに、トークンの発行をお願いすることになりますが、そのサーバーを示すURLは２つのパーツから成り立っています。ひとつはMicrosoft Azure が提供する共通の権威サーバーのドメインで、`https://login.microsoftonline.com`の部分にあたります。もうひとつは自分の組織（テナント）を表す部分で`テナント名.onmicrosoft.com`に該当します。これら２つの部分を合わせたURLがAuthority URLです。

`https://login.microsoftonline.com/あなたのテナント名.onmicrosoft.com`

> Tip: Authority URLの覚え方。`login`という文字を含むドメインはいかにも「認証」をイメージさせます。一方、`onmicrosoft.com`の`on`の部分はMicrosoftの上に乗っている「テナント」をイメージさせます。「認証のドメイン/テナントの名前」を合わせて「Authority（権威）」を指すURLになる、っと私は覚えるようにしてます。

`AuthenticationContext`クラスのインスタンスを得るためには、このようなAuthority URLを指定しなければいけません。Authority URLはいくつかのフォーマットがありますが、ここではテナントの名前を使ったAuthority URLを指定することにします。テナントの名前の代わりにテナントIDを使うこともあります。

`AuthenticationContext`が出来たら「認可サーバーとの会話が出来るようになった」と理解しましょう。認可サーバーはすべてのシナリオに参加する重要な登場人物です。

> `https://login.microsoftonline.com`の部分は古いサンプルなどでは`https://login.windows.net`という値を使っているものもあります。どちらでも機能しますが、新規に作成するアプリケーションでは`https://login.microsoftonline.com`を使いましょう。

### TODO_RESOURCEの値

コンソールアプリケーションが利用したいリソースはRESTful APIのエンドポイントとして表されます。この例ではMicrosoft Graph APIというサービス（リソース）を使うことにします。このリソースのエンドポイントの値は

`https://graph.microsoft.com`

なので、TODO_RESOURCEの値は`https://graph.microsoft.com`になります。

ところで、利用できるリソースはMicrosoftからはAzure Key Vault、VSTS、Office 365などがあります。また、ご自分のサービスをAADにアプリケーションとして登録すれば、そのサービスをリソースとして利用することも出来ます（ここではこのトピックには触れません）。

これで４つすべてのTODOアイテムの値が分かったので、`Program.cs`を変更してTODOアイテムを実際の値を差し替えましょう。(注：下の例はまだ完全ではありませんが、実際の値が代入されているとします。）

```c#:Program.cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace aad_client_console
{
    class Program
    {
        static void Main(string[] args)
        {
            var authContext = new AuthenticationContext("https://login.microsoftonline.com/あなたのテナント名.onmicrosoft.com");

            var authResult = authContext.AcquireTokenAsync(
                    "https://graph.microsoft.com",
                    "b0905193...で始まるGUID",
                    new Uri("https://あなたのテナント名.onmicrosoft.com/my-aad-client-console"),
                    new PlatformParameters(PromptBehavior.Always)).Result;

            Console.WriteLine("ID token: " + authResult.IdToken);
            Console.WriteLine("Access token: " + authResult.AccessToken);
        }
    }
}
```

これでコードは適切に更新されましたし、アクセスしたいリソースとしてMicrosoft Graph APIもきちんと指定しています。サクっとアクセストークンを認可サーバーから貰って、サクっとMicrosoft Graph APIを使ってみたいところですが、この状態ではMicrosoft Graph APIリソースにアクセスすることが出来るアクセス・トークンを発行してもらうことは出来ません。

なぜかというと、AADに登録したアプリケーション・オブジェクトに、明示的に「Microsoft Graph APIを使うよ！」と設定していないからです。この状態だと、AADがユーザーからそのために必要な許可を得ることが出来ません。何しろ「このアプリケーションがMicrosoft Graph APIを使う」という事実がどこにも明記されていないので。

ということでAADに、このコンソールアプリケーションが、Microsoft Graph APIを利用するということを予め、押しててあげなければなりません。

## AADアプリケーションにMicrosoft Graph APIへのアクセスを可能にさせる

AADに登録されたアプリケーションの「Settings」に「Required permissions」という項目があります（英語環境なので日本語での対訳が分かりませんが、アイコンで判別できるはずです）。

現在の設定は以下のようになっていて、アプリケーションがアクセスするリソースはWindows Azure Active Directoryのみとなっています。Microsoft Graph APIがここの表示されていないので、AADはユーザーとの会話で、アプリケーションがMicrosoft Graph APIにアクセスできるように取り計らうことが出来ない、という状況なのです。

![2018-07-19 16_49_34-Zoomit Zoom Window.jpg](https://qiita-image-store.s3.amazonaws.com/0/103139/04fa82d2-b59d-aec7-8ced-95e2b4dd43eb.jpeg)

では、Microsoft Graph APIへアクセスできるように設定を変更しましょう。「Add」ボタンを押しまうす。するとMicrosoftが提供する一般的なAPIがリスト表示されます（検索する必要はありません）。リストから「Microsoft Graph」を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/b22182db-ba0f-5e87-7396-3abe7260b1da.png)

次に許可を与える設定画面になります。いろいろと細かい設定が可能ですが、このサンプルでは「Read all users' basic profiles（すべてのユーザのベーシックなプロファイルを読む）」にチェックを入れて、保存します。これは、このコンソールアプリケーションはMicrosoft Graph APIが提供する様々な機能のうち、すべてのユーザーの基本的なプロファイル情報を読み込むこと（だけ）が出来る、という意味です。

> 許可する項目を増やせば増やすほど、よりパワフルなアプリケーションを作れますが、ユーザーもそれに応じてより慎重になります。原則としては「アプリケーションが必要な項目に限定して許可をする」が良いでしょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/af522c81-059d-0adc-1524-91eb42801472.png)

これで、このAADアプリケーション・オブジェクトが表すコンソールアプリケーションが、Microsoft Graph API を使うという意図が設定されました。設定はされましたが、実際に使えるかどうかはまだわかりません。その判断はユーザがAADとの会話の中で指定します。

実際の会話を、シナリオとして見ていきましょう。登場人物は以下の4人です。それぞれの立場を想像しながら見ていくと理解が深まるでしょう。

- ユーザー
- コンソールアプリケーション
- AAD認可サーバー
- Microsoft Graph API

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/5bf1c8b4-a073-8695-c431-3df6b25d691f.png)

#.NET Framework向けコンソールアプリケーションを実行する

プログラムをビルドしましょう。

ビルドはVisual StudioでもVSCodeでも、どちらにしろ`bin\Debug\net47`というディレクトリに`aad-client-console.exe`という実行ファイルが生成されます。

実行するとお馴染みのログイン画面が表示されます。これはAAD認可サーバがユーザーに対して「あなたがこのテナントのユーザーであることを証明してください（認証）」と聞いてきています。まずは身元確認ですね！

<img src="https://qiita-image-store.s3.amazonaws.com/0/103139/73d676fd-369d-1e8e-1494-556f9d2ceb17.png" width=50% >

認証が成功すると、次にまたお馴染みの許可を求める画面が表示されます。これはAAD認可サーバがユーザーに対して「あなたはこの、my-aad-client-consoleとかいうアプリケーションが以下の項目を実行することに対して、許可を与えますか？（認可）」と聞いてきています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/103139/6048dcb9-c5d1-e986-df31-f02b7f45051f.png" width=50% >

これら２つのダイアログが、以下のひとつのステートメントの一部として表示されました。このステートメントをアプリケーションの言葉として表すと、「認可サーバーさん、私はb0905193というクライエントなんですが、Microsoft Graph APIにアクセスできるトークンをくださいませんか？ユーザーさんには毎回かならず認証をしてもうってことでOKです。」といった感じになります。

```
var authResult = authContext.AcquireTokenAsync(
      "https://graph.microsoft.com",
      "b0905193...で始まるGUID",
      new Uri("https://あなたのテナント名.onmicrosoft.com/my-aad-client-console"),
      new PlatformParameters(PromptBehavior.Always)).Result;
```

> ここで、２つのダイアログが「表示された」ことに着目しましょう。ダイアログはポップアップウィンドウとして表示されましたが、Windowsプラットフォームでお馴染みの「ｘ」ボタンがついていました。これはWindows特有の、プラットフォームに依存する画面です。これは、このコンソールアプリケーションが.NET Framework向け（Windows環境のみで動作可能）としてビルドされたから可能なのです。.NET Core向けにビルドされたアプリケーションはこれが出来ません。

`authResult`にオブジェクトへのリファレンスが返ってきたら、そのオブジェクトの`IdToken`と`AccessToken`の値をそれぞれアウトプットに表示して、プログラムは終了します。

```
Console.WriteLine("ID token: " + authResult.IdToken);
Console.WriteLine("Access token: " + authResult.AccessToken);
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/ddbc0468-8ae4-e211-4e98-3f9e173f74e8.png)

> ここではIDトークンとアクセストークンの2つとも表示していますが、これら２つの使用目的は異なります。IDトークンは「認証」の結果であり、ユーザーに関する情報を含んでいます。一方、アクセストークンはMicrosoft Graph APIというリソースを利用する際にしようします。

# アクセストークンを使ってMicrosoft Graph API（リソース）にアクセスする

Microsoft Graph APIを利用できるアクセストークンを得ることが出来たので、とりあえず適当にユーザーを検索して基本情報を表示してみましょう。

下のソースはアクセストークンの内容を表示した後、`Tsuyoshi`というファーストネームを持つユーザーを返す、GetメソッドのHTTPリクエストを作成し、HttpClientインスタンスを作ってレスポンスを得るコードを書き足しました。

```c#:Program.cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace aad_client_console
{
    class Program
    {
        static void Main(string[] args)
        {
            var authContext = new AuthenticationContext("https://login.microsoftonline.com/あなたのテナント名.onmicrosoft.com");

            var authResult = authContext.AcquireTokenAsync(
                    "https://graph.microsoft.com",
                    "b0905193...で始まるGUID",
                    new Uri("https://あなたのテナント名.onmicrosoft.com/my-aad-client-console"),
                    new PlatformParameters(PromptBehavior.Always)).Result;

            Console.WriteLine("ID token: " + authResult.IdToken);
            Console.WriteLine("Access token: " + authResult.AccessToken);

            var request = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/users?$filter=givenName eq 'Tsuyoshi'");
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
            
            var client = new HttpClient();
            var response = client.SendAsync(request).Result;

            Console.WriteLine("User: " + response.Content.ReadAsStringAsync().Result);
        }
    }
}
```

HTTPリクエストに`Bearer`Authenticationヘッダを指定して、値として先のAAD認可サーバーに貰ったアクセストークをそのまま渡していることに留意してください。Microsoft Graphというリソースのサーバーは、このアクセストークンを検証することで、保護されているデータ（この場合、Tsuyoshiという名前のユーザーの基本情報）をクライエント（この場合、コンソールアプリケーション）に返すかどうかを決定できます）

> アクセストークンはBase64エンコードされてますが、デコードするとただのJSONになります。このJSONデータのスキーマがJWTです。誰が発行したとか、誰に向けて発行したとか、トークンがいつ期限切れになるかとか、いろんな情報が詰まってますが、リソースにアクセスする際には「まま」の状態で渡すだけでオッケーです。

アクセストークンは署名されてはいるので改竄を発見することができます。しかし暗号化はされていません。暗号化するための別の「包み方」も仕様としてはありますが、原則アクセストークンは丸見え前提でトランスポートレベルで暗号化しないといけません。SSL（HTTPSリクエスト）は絶対必須、という認識でオッケーです。

最後に、結果をコンソールに出力しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/807ac2a5-8aec-e650-4df3-f84a594ae326.png)

ユーザー（私）の基本情報を含むJSONのデータが見えるので、Microsoft Graph APIにアクセスしたことがデモされました。

# まとめ

この記事では以下の点での理解が深まったらな、と思います。

- AADはMicrosoft Azureが提供する認可サーバー
- AADはIDトークンとアクセストークンを発行してくれる（この違いに関しては別記事予定）
- コンソールアプリケーションでMicrosoft Graph API（他にKey Vaultとかも含めて）にアクセスできる
- AADと連携したコンソールアプリケーションの作り方とAAD側の設定
- AADと連携したコンソールアプリケーションの実際の実行時のシナリオと会話