はじめに
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 プロジェクトは適当に空のディレクトリに以下の2つのファイルを作成して、VSCodeで開いて開発環境とするか、Visual Studioで新規.NET Core コンソールアプリのプロジェクトを作成するか、どちらか好きな方で。
<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>
続いてメインとなるプログラムのコード。
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);
}
}
}
ロジックはこうです
- AuthenticationContextというオブジェクトを作ってAAD認証/認可サーバーを表す
- AAD認証/認可サーバーに「トークンを発行してください」とお願いする
- 発行された2つのトークン(IDトークンとアクセストークン)の内容を出力する
「AAD認証/認可サーバー」という呼び方は面倒だし一般的でないので今後は「AAD認可サーバー」と記します。認証と認可のサービスの境界線が実際には曖昧なことも混乱の原因なんですが、これはちょっと仕方ない状況なんで。そのうちなんとなく分かりますし、使えるレベルの理解には到達できます。
お気づきでしょうが、上のProgram.cs
には4つの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に実際にサービスエンドポイントなどがマップされている必要はありません。)

登録すると、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の箇所にこの値を代入します。

残るはTODO_AUTHORITY_URL
とTODO_RESOURCE
の二つですが、どちらもすでにある情報から値がわかります。以下、ひとつずつ見ていきましょう。
TODO_AUTHORITY_URLの値
Authority(権威?)は、セキュリティートークンを発行してもらうSecurity Token Service (STS)もしくは authorization server (認可サーバー)のことで、Microsoft AzureではAzure Active Directoryがこれらのサービスを実装しています。Authority URLはこのサーバーを指し示すコネクションストリングと理解すればよいでしょう。
このURLの背後にあるサーバーに、トークンの発行をお願いすることになりますが、そのサーバーを示すURLは2つのパーツから成り立っています。ひとつはMicrosoft Azure が提供する共通の権威サーバーのドメインで、https://login.microsoftonline.com
の部分にあたります。もうひとつは自分の組織(テナント)を表す部分でテナント名.onmicrosoft.com
に該当します。これら2つの部分を合わせた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にアプリケーションとして登録すれば、そのサービスをリソースとして利用することも出来ます(ここではこのトピックには触れません)。
これで4つすべてのTODOアイテムの値が分かったので、Program.cs
を変更してTODOアイテムを実際の値を差し替えましょう。(注:下の例はまだ完全ではありませんが、実際の値が代入されているとします。)
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にアクセスできるように取り計らうことが出来ない、という状況なのです。
では、Microsoft Graph APIへアクセスできるように設定を変更しましょう。「Add」ボタンを押しまうす。するとMicrosoftが提供する一般的なAPIがリスト表示されます(検索する必要はありません)。リストから「Microsoft Graph」を選択します。
次に許可を与える設定画面になります。いろいろと細かい設定が可能ですが、このサンプルでは「Read all users' basic profiles(すべてのユーザのベーシックなプロファイルを読む)」にチェックを入れて、保存します。これは、このコンソールアプリケーションはMicrosoft Graph APIが提供する様々な機能のうち、すべてのユーザーの基本的なプロファイル情報を読み込むこと(だけ)が出来る、という意味です。
許可する項目を増やせば増やすほど、よりパワフルなアプリケーションを作れますが、ユーザーもそれに応じてより慎重になります。原則としては「アプリケーションが必要な項目に限定して許可をする」が良いでしょう。
これで、このAADアプリケーション・オブジェクトが表すコンソールアプリケーションが、Microsoft Graph API を使うという意図が設定されました。設定はされましたが、実際に使えるかどうかはまだわかりません。その判断はユーザがAADとの会話の中で指定します。
実際の会話を、シナリオとして見ていきましょう。登場人物は以下の4人です。それぞれの立場を想像しながら見ていくと理解が深まるでしょう。
- ユーザー
- コンソールアプリケーション
- AAD認可サーバー
- Microsoft Graph API
.NET Framework向けコンソールアプリケーションを実行する
プログラムをビルドしましょう。
ビルドはVisual StudioでもVSCodeでも、どちらにしろbin\Debug\net47
というディレクトリにaad-client-console.exe
という実行ファイルが生成されます。
実行するとお馴染みのログイン画面が表示されます。これはAAD認可サーバがユーザーに対して「あなたがこのテナントのユーザーであることを証明してください(認証)」と聞いてきています。まずは身元確認ですね!

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

これら2つのダイアログが、以下のひとつのステートメントの一部として表示されました。このステートメントをアプリケーションの言葉として表すと、「認可サーバーさん、私は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;
ここで、2つのダイアログが「表示された」ことに着目しましょう。ダイアログはポップアップウィンドウとして表示されましたが、Windowsプラットフォームでお馴染みの「x」ボタンがついていました。これはWindows特有の、プラットフォームに依存する画面です。これは、このコンソールアプリケーションが.NET Framework向け(Windows環境のみで動作可能)としてビルドされたから可能なのです。.NET Core向けにビルドされたアプリケーションはこれが出来ません。
authResult
にオブジェクトへのリファレンスが返ってきたら、そのオブジェクトのIdToken
とAccessToken
の値をそれぞれアウトプットに表示して、プログラムは終了します。
Console.WriteLine("ID token: " + authResult.IdToken);
Console.WriteLine("Access token: " + authResult.AccessToken);
ここではIDトークンとアクセストークンの2つとも表示していますが、これら2つの使用目的は異なります。IDトークンは「認証」の結果であり、ユーザーに関する情報を含んでいます。一方、アクセストークンはMicrosoft Graph APIというリソースを利用する際にしようします。
アクセストークンを使ってMicrosoft Graph API(リソース)にアクセスする
Microsoft Graph APIを利用できるアクセストークンを得ることが出来たので、とりあえず適当にユーザーを検索して基本情報を表示してみましょう。
下のソースはアクセストークンの内容を表示した後、Tsuyoshi
というファーストネームを持つユーザーを返す、GetメソッドのHTTPリクエストを作成し、HttpClientインスタンスを作ってレスポンスを得るコードを書き足しました。
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リクエスト)は絶対必須、という認識でオッケーです。
最後に、結果をコンソールに出力しています。
ユーザー(私)の基本情報を含むJSONのデータが見えるので、Microsoft Graph APIにアクセスしたことがデモされました。
まとめ
この記事では以下の点での理解が深まったらな、と思います。
- AADはMicrosoft Azureが提供する認可サーバー
- AADはIDトークンとアクセストークンを発行してくれる(この違いに関しては別記事予定)
- コンソールアプリケーションでMicrosoft Graph API(他にKey Vaultとかも含めて)にアクセスできる
- AADと連携したコンソールアプリケーションの作り方とAAD側の設定
- AADと連携したコンソールアプリケーションの実際の実行時のシナリオと会話