LoginSignup
6
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-21

はじめに

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 コンソールアプリのプロジェクトを作成するか、どちらか好きな方で。

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>

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

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. 発行された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_URLTODO_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.comonの部分は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アイテムを実際の値を差し替えましょう。(注:下の例はまだ完全ではありませんが、実際の値が代入されているとします。)

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

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

image.png

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

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

image.png

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

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

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

image.png

.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にオブジェクトへのリファレンスが返ってきたら、そのオブジェクトのIdTokenAccessTokenの値をそれぞれアウトプットに表示して、プログラムは終了します。

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

image.png

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

アクセストークンを使ってMicrosoft Graph API(リソース)にアクセスする

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

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

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

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

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

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

image.png

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

まとめ

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

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