こんにちは、Azure ID チームの埴山です。
本記事は Azure Tech Advent Calendar 4 日目の記事です。
今回はトラブルシューティングの方法ではなく、Azure AD を利用したアプリケーション開発における、”API 権限” について説明します。特に Microsoft Graph API を題材に API エコシステムの中身を見ていきます。
Azure AD を利用してアプリ開発を検討している方や、Azure AD における OAuth の実装について詳しく知りたい方への記事となります。
チュートリアルなどについては、一通り動かしたことのある方を対象とした記事ですので、Azure AD で保護されたアプリケーション開発の基礎については Microsoft ID プラットフォームのドキュメント | Microsoft Docs の公開ドキュメントをご確認ください。
OAuth と Azure AD
OAuth は「リソース」へのアクセス権を安全に委譲し、API を保護するための「トークン」を発行する仕組みのデファクトスタンダードです。Microsoft では Exchange Online や SharePoint Online といった「リソース」に対するアクセスするための「API」を OAuth の仕組みを使って保護しており、ユーザーは必要な権限を必要に応じて「クライアント」であるアプリに払い出すことが可能です。これらの権限管理を行っているのが Azure AD です。
ここでは OAuth の基礎については説明を省きますが、Azure AD にアプリを登録することで、Microsoft が管理する Azure AD で保護された「リソース」へのアクセスを行う「クライアント」を作成したり、自社で開発した API を「リソース」として登録し Azure AD で保護したりすることができます。リソースへのアクセス権は Azure AD では API のアクセス許可とよばれます。
API のアクセス許可は「トークン」という形で「クライアント」であるアプリに受け渡されます。例えばユーザーが、ユーザー情報を取得できる API のアクセス許可を、クライアントであるアプリに許可することが可能です。アクセス許可を付与されたアプリケーションは、アクセス トークンというトークンを取得し、ユーザーの情報を取得する API を呼び出すことが可能になります。
アプリが取得できる権限として、ユーザー委任のアクセス許可と、アプリケーション権限のアクセス許可の二つがあります。前者がユーザーの同意を得て、ユーザーの権限相当のアクセス権を取得できるのに対し、後者は管理者がアプリに権限を付与 (管理者の同意) することで、アプリケーション自身に権限を付与し、アプリ自身が API のアクセス権を得ることができます。
ただし、あくまでここで取得できる権限は API のアクセス許可であり、実際のリソースへのアクセス権はリソースオーナー、つまり Exchange Online や SharePoint Online が管理していることに注意してください。たとえば、あるユーザーがユーザー委任のメールの読み込み権限をアプリに付与したからといって、対象のユーザーがアクセスできないメールボックスの読み込みを行うことはできません。つまり、ユーザーがアクセスできるリソースの範囲は、API のアクセス許可と、リソース側の権限管理の両方で制御されています。
本記事ではこれらの API のアクセス許可の実体について深堀していきます。
サンプルアプリを動作させる
まずは実際に Microsoft Graph API を呼び出すサンプルを動かし、その動作を見てみましょう。
後程、テナントに同意された権限などを確認するために、テナントのグローバル管理者権限が必要となってきます。そのためサンプルの動作はテストテナントを作成しお試しいただくことをお勧めします。
また今回紹介するアプリの実際の動作のためには Node.js のインストールが必要です。
Note
Microsoft Graph API を呼び出すちょうどいいサンプルが、node.js だったので
アプリの登録とサンプルのダウンロード
Azure Active Directory > アプリの登録 から新規アプリを登録します。
名前は MS Graph Client Sample
としましょう。
設定はデフォルトのまま作成します。そのままクイック スタート
をクリックし、シングルページ アプリケーション (SPA)
を選択します。
続いて JavaScript (認証コード フロー) を選択します。
これらの変更を行います
ボタンをクリックし、サンプルの動作に必要な設定を変更します。
続けてプロジェクトのダウンロードです。 コード サンプルをダウンロードします
をクリックしダウンロードした zip ファイルを適当な場所に展開します。
サンプルの動作
PowerShell を起動し、コードサンプルを展開したフォルダーに移動します。ちなみにエクスプローラーの検索窓に powershell と入力すると、開いているフォルダーから PowerShell を起動できます。
クイック スタートにある通り npm install および npm start コマンドを実行します。
npm install |
http://localhost:3000/
でアプリが起動しますので、ブラウザーでアクセスします。
動作の確認
Sign In
ボタンをクリックし、ユーザーの資格情報を入力すると API のアクセス許可の同意画面が出てきます。
承諾
をクリックするとこで、サインインが完了します。この際、Azure AD からアプリに対し ID トークンとアクセス トークンが発行されています。
Note
※ 今回は ID トークンの詳細や使い道については説明しません。
See Profile
ボタンをクリックすることで、取得したアクセス トークンを使ってユーザーのプロファイル情報を取得できます。
次に Read Mails
ボタンをクリックしてみましょう。
すると、新たに Read your mail
の API のアクセス許可を求める同意画面が表示され、同意
をクリックすることでメールを読み込むためのトークンが取得されます。
※ テストユーザーは Exchange Online のライセンスを持っていなかったので API の呼び出しには失敗しています。
何がおこったのか
これまでの操作をまとめてみましょう。
今回、新しいアプリケーションを Azure AD に登録し、ユーザー プロファイルの表示ができる API のアクセス許可と、メールの読み込みの API のアクセス許可を要求しました。
要求に対しユーザーの同意画面が現れ、同意を行うことでアプリがアクセス トークンを取得できるようになりました。
アプリは取得したアクセス トークンを使って Microsoft Graph API を呼び出し、ユーザー情報やメールの読み込みを行いました。
これらの裏でどのようなことが起こっていたのでしょうか。より詳細に見てみましょう。
エンタープライズ アプリケーションと API のアクセス許可
ユーザーが作成したクライアント アプリは、Azure ポータルの “アプリの登録” に登録されます。と、同時に API の権限の同意結果を保存する “エンタープライズ アプリケーション” が作成されます。
詳細を確認するために作成したアプリのプロパティに表示される “ローカル ディレクトリでのマネージド アプリケーション” をクリックしてみましょう。(Azure Active Directory
> エンタープライズ アプリケーション
と移動し検索しても OK です。)
表示されたサービス プリンシパルの アクセス許可
を確認します。ユーザー同意のタブを見ると、先ほどユーザーが同意した API のアクセス許可が登録されています。
詳細を確認すると、たとえば User.Read
のスコープに同意済みであることなどが確認できます。
4 つのアクセス許可をよく見てみると、クレームの値が openid
, profile
, User.Read
, Mail.Read
であることが確認できるかと思います。
API のアクセス許可と scope
これらの 4 つのクレームの値は何を表しているのでしょうか。
実は先ほどのサインイン時に、アプリケーションは Azure AD の認可エンドポイントに対し、scope を指定し認可要求 (承認コードを要求)を送っていました。
要求するパラメーターはサンプルの app/authConfig.js
内で、以下のように指定されています。
// Add here the scopes that you would like the user to consent during sign-in |
コードを確認していただければわかりますが loginRequest
がサインイン時に、tokenRequest
がメール読み込み時に要求される scope です。
Note
※ msal.js では loginPopup 呼び出し時、自動で socpe に openid profile を追加するため、最終的に同意した API の権限は、openid
, profile
, User.Read
, Mail.Read
の 4 つとなります。
つまり、ユーザー委任の API のアクセス許可イコール、scope と呼ばれる単位で管理されていることがわかります。
このようにユーザーがアプリに同意を行うことで、ユーザーの同意情報がサービス プリンシパルに保存され、アプリはユーザーの代わりに API の呼び出しを実施することが可能になるのです。
Microsoft Graph API
では、ここで同意された User.Read
のスコープとはなんなのでしょうか。Microsoft Graph API の API のアクセス許可であることはご存じだと思いますが、これらのスコープはどこに定義されて、同意した権限はどのように保存されているのでしょうか。
OAuthPermissionGrants を確認する
これを理解するためには、Azure AD に登録されたサービス プリンシパルについて確認することが必要です。
先ほど “アプリの登録” からアプリを作成したと思います。その際エンタープライズ アプリケーションも同時に作成されると言いましたが、このエンタープライズ アプリケーションこそがサービス プリンシパルと呼ばれるオブジェクトです。正確にはエンタープライズ アプリケーションがサービス プリンシパルの一種ですが基本的には “サービス プリンシパル” と “エンタープライズ アプリケーション” はほぼ同義だと考えて OK です。
テナントに登録された “サービス プリンシパル” オブジェクト の一覧は、Azure AD PowerShell モジュールや Microsoft Graph API で取得できます。
ここでは Microsoft Graph PowerShell SDK を利用して、サービス プリンシパルの一覧を取得してみましょう。
Install-Module Microsoft.Graph |
OAuth の権限を取得するため、Directory.Read.All
の権限が必要ですので、あらかじめ指定しておきます。この後の Connect-Graph 実行時にはテナントのグローバル管理者でのサインインが必要です。
Connect-Graph -Scopes Directory.Read.All |
このテナントはテスト用に作成したテナントなので、これだけしか出力されませんが、実際のテナントでは Microsoft が管理しているアプリや、サードパーティー製のアプリ、自社で開発しているアプリなど、大量に出力されると思います。
これらの多くのアプリケーションは Azure ポータルの エンタープライズ アプリケーション にも表示されていますが、Microsoft が管理されている一部のサービス プリンシパルはポータルに表示されないものもあります。
今回作成したアプリは MS Graph Client Sample
ですので、当該のサービス プリンシパル取得しておきます。
$myAppSp = Get-MgServicePrincipal -Filter "displayName eq 'MS Graph Client Sample'" |
次に、先ほど同意を行った API のアクセス許可を取得します。API のアクセス許可は OAuth2Permissions として保存されているため、以下のコマンドで取得可能です。
$oAuth2Permissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $myAppSp.Id |
先ほどのサンプルで同意した API のアクセス許可である User.Read, openid, profile, Mail.Read が表示されています。
また PrincipalId は、先ほど同意処理を実施したユーザーの ObjectId であることが確認できます。
Get-MgUser -UserId e1bf6875-6eea-47f5-abf9-eee6f5dd6cbe |
つまり、この情報から上記ユーザーが、”User.Read, openid, profile, Mail.Read” の権限を、クライアントであるサービス プリンシパル “439775c2-70a1-4e08-a3e7-cd39c50e918d” (MS Graph Client Sample) に委任した、ということが確認できます。
そして、それらの情報は OAuth2Permissions として保存されていることがわかります。
さて、この API 権限 (OAuth2Permissions) ですがサンプルを動作させた皆様なら、”Microsoft Graph API” の権限であることはお分かりかと思います。
では、その情報はどこで確認できるのでしょうか。
OAuth2PermissionGrant の内容を確認すると ResourceId “19a9419c-cc6f-47c6-88f3-0f2a964a4f16” とあります。
この ResourceId を指定し、サービス プリンシパルを取得してみましょう。
Get-MgServicePrincipal -ServicePrincipalId 19a9419c-cc6f-47c6-88f3-0f2a964a4f16 | Select-Object DisplayName, AppId |
Note
※ 表示される ResourceId はテナントごとに異なります。
リソースの正体
当たり前といえば当たり前ですが、ResourceId が指示していたのは Microsoft Graph API でした。
そしてその正体は、テナントに登録されている “Microsoft Graph” という名前のサービス プリンシパルなのです。
実は Azure AD の “サービスプリンシパル” は、OAuth の文脈では “クライアント” を表すこともあれば、”リソース” を表すこともあります。(あるいはその両方を表すこともあります)
そして ResourceId に表示されている通り、Microsoft Graph は Azure AD に登録されたリソースとしてのサービス プリンシパルである、ということです。
それでは早速 Microsoft Graph API のリソースを表す、”Microsoft Graph” の “サービスプリンシパル” の中身を見てみましょう。
サービスプリンシパルは先ほどの ResourceId を指定するか、もしくは、Microsoft Graph の AppId は “00000003-0000-0000-c000-000000000000” と決まっているので、以下のようにフィルターしても OK です。
$msGraph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'" |
取得したサービス プリンシパルを確認してみましょう。
$msGraph | Format-List * |
設定値がいくつか表示されますね。たとえば PublisherName
は Microsoft Services
で、AppOwnerOrganizationId
が f8cdef31-a31e-4b4a-93e4-5f571e91255a
であるので、Microsoft が管理しているサービス プリンシパルであると判別できます。
ここで今回注目すべきは Oauth2PermissionScopes です。
少し中身を確認してみましょう。
$msGraph.Oauth2PermissionScopes | Select-Object Value, UserConsentDescription -First 10 |
そうです、もうお分かりですね。Microsoft Graph API の scope 一覧は、この Oauth2PermissionScopes に定義されています。
ちなみに User.Read
権限は、ユーザーをアプリにサインインさせ、プロファイルを表示させる権限と定義されています。
$ $msGraph.Oauth2PermissionScopes | ?{ $_.Value -eq "User.Read"} | Select-Object Value, UserConsentDescription |
Microsoft Graph API のアクセス許可の一覧は、Microsoft Graph のアクセス許可のリファレンス - Microsoft Graph | Microsoft Docs にあります。
ユーザー権限の API のアクセス許可、すなわち scope はこのうち委任されたアクセス許可に該当します。
ユーザー委任の API のアクセス許可のまとめ
Microsoft Graph API をユーザー委任の権限で呼び出すアプリを実装しました。
アプリは scope と呼ばれる単位で、ユーザーに API のアクセス許可の同意を要求し、ユーザーが同意することでそれらのアクセス許可を委任されます。
ユーザーの同意済みスコープはエンタープライズ アプリケーション (サービス プリンシパル) に保存されますが、これらのスコープはもともと Microsoft Graph のサービス プリンシパルに OAuth2PermissionScopes として定義されています。
API の呼び出しに必要な scope を含むアクセス トークンを取得することで、Microsoft Graph API を呼び出すことができました。
アプリケーションのアクセス許可
ここまではユーザー委任のアクセス許可について説明しましたが、アプリケーションのアクセス許可についても少し説明します。とはいえ、ユーザー委任の権限に比べるとシンプルです。
ユーザー委任の権限は、scope という単位で、アクセス時にユーザーに同意を求める動作でした。
それに対し、アプリケーションのアクセス許可はアプリの作成時に事前に定義を行います。
アプリケーションを作成し、アクセス許可を付与する
アプリケーションのアクセス許可の付与方法については、以前ブログで ユーザーの最終サインイン日時を取得するスクリプト を紹介しましたので、こちらをサンプルとして利用します。
上記サンプルでは、User.Read.All
と AuditLog.Read.All
のアプリケーションのアクセス許可をアプリに付与し、管理者の同意を実施していました。
そしてアプリは保存されたクライアント シークレットや証明書を利用し、Microsoft Graph API を呼び出すためのアクセス トークンを取得しています。
アプリケーションの権限を付与することで何が起きたのか
先ほどと同様に、クライアントとして登録したアプリのサービス プリンシパルを確認してみましょう。
アクセス許可を確認すると、種類が Application となっており、クレームの値は User.Read.All となっています。また、付与方法が “管理者の同意” となっていることも確認できます。
つまり、今回も User.Read.All の scope に同意したということなのでしょうか。
先ほどと同じように、同意済みの scope を Get-MgOauth2PermissionGrant コマンドで確認してみましょう。
$myAppSp = Get-MgServicePrincipal -Filter "displayName eq 'Get Last SignIn'" |
残念ながら、デフォルトで設定されている User.Read
※ のユーザー権限のアクセス許可のみが表示されており、User.Read.All
や AuditLog.Read.All
のアクセス許可は見当たりません。
Note
※ デフォルトで設定されている User.Read
のアクセス許可は、サインインログの取得に特に必要ない権限ですが特に削除の必要もないため削除しておりませんでした。ユーザー委任のアクセス許可にもこのように管理者の同意ができます。
アプリケーション権限の正体
結論を述べると、アプリケーション権限の正体は scope ではありません。
アプリケーション権限のアクセス許可は AppRole であり、同意済みの権限は AppRoleAssignments と呼ばれ PowerShell だと以下のコマンドで取得できます。
$appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $myAppSp.Id |
先ほどと同じように、ResourceId 19a9419c-cc6f-47c6-88f3-0f2a964a4f16
の Microsoft Graph の権限が 2 つ現れました。
おそらくどちらかが User.Read.All
で、どちらかが AuditLog.Read
のはずなのですが、これらの情報からどうやって確認すればよいのでしょうか。
もちろん、これらの値は Microsoft Graph のサービス プリンシパルに定義されています。
具体的にアプリケーションの権限の API のアクセス許可は AppRole として定義されています。
$msGraph = Get-MgServicePrincipal -ServicePrincipalId 19a9419c-cc6f-47c6-88f3-0f2a964a4f16 |
Id からアクセス許可の値を逆引きすると、アプリに割り当てられた AppRole の権限を確認することができます。
$appRoleId = $appRoleAssignments | %{ $_.AppRoleId } |
アプリケーション権限の API のアクセス許可まとめ
まとめると、アプリケーション権限のアクセス許可は、AppRole として定義されていることを確認しました。
アプリケーション オブジェクトに API のアクセス許可を設定し、管理者の同意を行うことでサービス プリンシパルに AppRoleAssignment として許可された API のアクセス許可が保存されます。
そしてアクセス トークンには roles として、API のアクセス許可が含まれ、Microsoft Graph API ではこのクレームをもとに、API アクセスを制御できることを確認しました。
Next Step
ここまでは、Microsoft Graph API の scope と AppRole について解説をしてきましたが、Microsoft ID プラットフォームでは独自の API を作成し、Azure AD で保護することも可能です。
Azure AD 上にリソースとしてのアプリを登録し、scope や AppRole を定義することにより、自社の API を Azure AD の ID エコシステムを利用し保護することが可能です。つまり、Azure AD 上で特定のユーザーのみがアクセス可能なように制御したり、条件付きアクセスポリシーで不正なアクセスから保護するといったことが可能になります。
サンプルアプリ
Azure AD で API を保護するには、クライアント用のアプリに加え API 用のアプリを登録し、scope や AppRole を定義します。
クライアントでは MSAL ライブラリを利用し独自の API を呼び出すアクセス トークンを取得し、API 側では Microsoft.Identity や OWIN ライブラリなどを利用することにより、提示されたアクセス トークンを検証し、正規のトークンを所持するユーザー以外のアクセスをブロックすることが可能です。
具体的なサンプルは クイックスタート: Microsoft ID プラットフォームによって保護されている ASP.NET Web API を呼び出す をご参照ください。また 各種言語のサンプル は GitHub に公開されているか探してみてください。
今回は JavaScript の API サンプルを動かし、先ほど作った MS Graph Client Sample から独自の API を呼び出してみます。
最終的な構成は以下のようなイメージです。
アプリの登録
まずはクライアント アプリ同様に、アプリの登録画面から API を登録します。
scope の登録
scope つまり、OAuth2Permissions つまり、ユーザー委任のアクセス許可は API の公開から登録します。
scope を登録するには、事前にアプリケーション ID の URI (Identifier) を設定します。これは OAuth の audience (aud)、リソースを表す識別子となります※。例えば Microsoft Graph API では “https://graph.microsoft.com" です。
この値はグローバルで一意である必要があり、初期設定では “api://appid” です。
Note
※ アクセス トークン (V2) に含まれる aud の値はアプリケーション ID で固定です
スコープ名は Microsoft Graph API に倣って XXX.Read のように設定しましたがアプリの構成に従い好きな名前をつけて OK です。細かいスコープを切らない場合、Microsoft のサービスでは “user_impersonation” がよく利用されます。
スコープの追加
をクリックすると、スコープが追加されます。
scope を指定する際には、本来このアプリケーション ID の URI と scope の値を /
でつなげて指定する必要があります。今回の例では “api://5a6e76a2-0790-4ca7-b42e-27b31d70aa92/Data.Read” が指定すべき scope の値になります。
重要
scope の指定時にアプリケーション ID の URI を省略すると暗黙的に “https://graph.microsoft.com" が指定されます。
AppRole の追加
実は AppRole の設定は最近 Azure ポータルから行えるようになりました。アプリのロール (プレビュー)
にアクセスし、アプリ ロールの作成
をクリックします。
アプリケーションに付与できる権限を定義する場合には、許可されたメンバーの種類
を “アプリケーション” か、”両方” を選択します。
なぜ、アプリケーション権限の実態である Role をユーザーに対しても割り当てられるんだと思われるかもしれませんが、実はアプリケーションの AppRole だけでなく、ユーザーの AppRole を Azure AD に定義することもできます。アプリ ロールを追加してトークンから取得する により詳細が記載されているので、ロールベースのアクセス許可を Azure AD で実現されたい方はご覧ください。
このように、AppRole についても Azure ポータルから設定が可能です。
マニフェスト
に移動し、後の動作確認のために accessTokenAcceptedVersion を 2 に設定し、保存します。
Note
トークンのバージョンについての詳細は Microsoft ID プラットフォームのアクセス トークン - Microsoft Entra | Microsoft Learn をご確認ください
動作の確認
最後に、定義した API のアクセス許可の動作を確認してみます。
クライアントの修正
まず自作の API を呼び出すためのアクセス トークンを取得します。アプリケーション権限の付与方法は、Graph API の場合と同じく、API のアクセス許可からアプリケーション権限の API を選択するだけですので省略します。
ユーザー権限のアクセス許可については、クイックスタートのサンプル (MS Graph Client Sample) を改造して呼び出しのイメージをつかんでみましょう。
いくつか修正点があるので順に説明します。
まずは、authConfig.json
に myAPITokenRequest
オブジェクトを作成しましょう。もともとの tokenRequest
の scopes を “api://5a6e76a2-0790-4ca7-b42e-27b31d70aa92/Data.Read” に変更するだけです。
authConfig.json
// my api token request |
API 呼び出し部分は、callMSGraph メソッドを流用し、http://localhot:5000/api
を呼び出します。
authPopup.js
function callMyAPI() { |
ui.js と index.html にもコードを追加します。
ui.js
if (endpoint === graphConfig.graphMeEndpoint) { |
index.html
<button class="btn btn-primary" id="seeProfile" onclick="seeProfile()">See Profile</button> |
これでクライアント部分の実装は終了です。
API の実装
API では提示されたトークンが正規のものか、署名を検証し、必要なクレームの値 (例えば aud など) が含まれているかを検証する必要があります。
定義した API を保護するためのサンプルとしては、JavaScript の Passport を利用した以下のものを利用します。
config.json を以下のように修正します。
"credentials": { |
修正後、以下のコマンドで API を起動します。正常に起動できれば http://localhost:5000 で API サーバーが動き出します。
npm install |
動作確認
最後に http://localhost:3000 にアクセスして、アプリにサインインしましょう。Call MyAPI
をクリックすると先ほど定義した scope に同意するよう求められます。
承諾をクリックすると、http://localhost:5000/api
の API を呼び出すためのトークンが取得され、無事 API を呼び出すことに成功します。
最終的な構成はこのようなイメージです。
まとめ
今回は Microsoft Graph API を呼び出すサンプルと、独自の API を定義することで、Azure AD 上で OAuth の scope や AppRole の定義方法やその実体を見てみました。チュートリアルをこなすだけでは見えてこない詳細なイメージがつかめたのではないでしょうか。
スコープや AppRole について理解することは、独自のアプリを実装するときだけでなく Microsoft が提供する API を利用する際にも非常に役に立つと思います。
さらに、独自のアプリを実装するに当たっては、Azure AD のシングルテナント アプリとマルチテナント アプリ の違いを理解したり、Microsoft が推奨するベストプラクティスのチェックリストを確認したりするのがよいと思います。
皆様もぜひ、Azure AD を利用したセキュアなアプリ開発を実践してみてください。
※本情報の内容(添付文書、リンク先などを含む)は、作成日時点でのものであり、予告なく変更される場合があります。