Azure Active Directory の Graph API の活用

開発者にとっての Microsoft Azure Active Directory

こんにちは。

Azure Active Directory Graph (Graph API) を使うと、REST の呼び出しで、ユーザーの作成・変更をおこなうような管理機能を提供したり、アプリケーション上でユーザー一覧 (ユーザー選択画面など) を提供したり、後述のようにバックエンドでユーザー情報を同期することなども可能です。(特に同期ソリューションは、Azure AD とカスタム アプリ (SaaS) を Federation させる上で重要な機能です。)

今回は、この Graph API について、その利用手順と概念を解説します。

2015/11 追記 :  本投稿で紹介する Azure AD Graph API も含め、Microsoft が提供するすべてのサービス / データの API が Microsoft Graph API に統一されました。(詳細は「Active Directory Team Blog : Introducing the Microsoft Graph –The Azure AD GraphAPI goes big time!」をご参照ください。)
今後は、 Microsoft Graph で下記と同様の開発 (プログラミング) が可能です。

 

認証処理と Access Token の取得

Graph API (Url は https://graph.windows.net/<tenant realm>) は後述で見ていくように REST で提供されていますが、この API 自身も Azure AD 上の Service Application (Web API) として、OAuth 2 による Access Token を取得してアクセスします。

これまで述べてきたように Access Token の取得方法はいくつかあります。(以下のいずれかの方法で取得できます。)

  • Azure AD を使った API (Service) 連携の Client 開発」で紹介したフローを使って、ログイン画面を表示して Access Token を取得できます。(ログイン画面を使って、ユーザーにログインさせます。)
    ここで述べるような Azure AD テナントの情報の読み込み・書き込みをおこなうには、「Azure Active Directory の Common Consent Framework」で記載している方法で、作成する Application (Client) の Permission (アクセス許可) として [Read and write directory data] を設定します。(この場合、必ず、管理者のアカウントでログインする必要があります。)
  • その他の方法として、「Azure AD : Backend Server-Side アプリの開発 (Deamon, Service など)」で解説した方法で Application Permissions を使って Access Token を取得できます。この場合、特定ユーザーではログインしないので、ログイン画面は表示されません。(この方法は、バックエンドの同期アプリなどで使えます。)

補足 : もし、People Picker のような一般ユーザーが使う機能を実装するときには、[Read all users’ basic profile] などの新しく追加された Delegated Permission を使用してください。この場合、管理者によるログインは不要です。(「New graph API consent permissions」を参照してください。)
ここでは、Azure AD の管理を目的とした使用方法を紹介するので、管理者権限が必要です。

なお、.NET (C# など)、Cordova、Objective-C、Android SDK などを使用されている方は、Active Directory Authentication Library (ADAL) を使用して Access Token を取得しても構いません。

. . .
using Microsoft.IdentityModel.Clients.ActiveDirectory;
. . .

private void button1_Click(object sender, EventArgs e)
{
  //
  // Get access token using login UI
  //
  AuthenticationContext context =
    new AuthenticationContext("https://login.microsoftonline.com/o365demo01.onmicrosoft.com");
  AuthenticationResult assertion = context.AcquireToken(
    "https://graph.windows.net",  // resource (graph api)
    "39ff4d28-9cba-448c-acae-612705e54257",  // client id
    new Uri("https://localhost/sampleapp"));
  string authHeader = assertion.CreateAuthorizationHeader();
}
. . .

補足 : Azure Team Blog で紹介しているように、.NET 版の Active Directory Authentication Library (ADAL) の version 0.6 以降では、Full Managed (100% .NET) になりました。
0.6 以前では、 環境の相違 (x86, x64) に注意してください。

 

Graph API の呼び出し

つぎに、上記で取得した Access Token を使って、Graph API の REST サービスからデータを取得します。

例えば、下記の HTTP Request は o365demo01.onmicrosoft.com の Directory の User 一覧を取得します。
api-version の指定は必ず必要です。(現在サポートされている api-version については「MSDN : Azure AD Graph API Versioning」を参考にしてください。)

GET https://graph.windows.net/o365demo01.onmicrosoft.com/users
Authorization: Bearer {access token}
api-version: 1.0

結果 (HTTP Response) は以下の通りです。

HTTP/1.1 200 OK
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Access-Control-Allow-Origin: *

{
  "odata.metadata": "https://graph.windows.net/o365demo01.onmicrosoft.com/$metadata#directoryObjects/Microsoft.WindowsAzure.ActiveDirectory.User",
  "value": [
    {
      "odata.type": "Microsoft.WindowsAzure.ActiveDirectory.User",
      "objectType": "User",
      "objectId": "ca3645ea-e265-42c0-b1ff-bfb467c0352e",
      "accountEnabled": true,
      "assignedLicenses": [
        {
          "disabledPlans": [
            
          ],
          "skuId": "3b555118-da6a-4418-894f-7df1e2096870"
        }
      ],
      "assignedPlans": [
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Enabled",
          "service": "SharePoint",
          "servicePlanId": "c7699d2e-19aa-44de-8edf-1736da088ca1"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Enabled",
          "service": "exchange",
          "servicePlanId": "9aaf7827-d63c-4b61-89c3-182f06f82e5c"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Deleted",
          "service": "RMSOnline",
          "servicePlanId": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Deleted",
          "service": "MicrosoftOffice",
          "servicePlanId": "43de0ff5-c92c-492b-9116-175376d08c38"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Enabled",
          "service": "MicrosoftCommunicationsOnline",
          "servicePlanId": "0feaeb32-d00e-4d66-bd5a-43b5b83db82c"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Enabled",
          "service": "SharePoint",
          "servicePlanId": "e95bec33-7c88-4a70-8e19-b10bd9d0c014"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Deleted",
          "service": "SharePoint",
          "servicePlanId": "5dbe027f-2339-4123-9542-606e4d348a72"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:28Z",
          "capabilityStatus": "Deleted",
          "service": "exchange",
          "servicePlanId": "efb87545-963c-4e0d-99df-69c6916d9eb0"
        }
      ],
      "city": null,
      "country": null,
      "department": null,
      "dirSyncEnabled": null,
      "displayName": "Taro Demo",
      "facsimileTelephoneNumber": null,
      "givenName": "Taro",
      "jobTitle": null,
      "lastDirSyncTime": null,
      "mail": "demouser01@o365demo01.onmicrosoft.com",
      "mailNickname": "demouser01",
      "mobile": null,
      "otherMails": [
        
      ],
      "passwordPolicies": "DisablePasswordExpiration",
      "passwordProfile": null,
      "physicalDeliveryOfficeName": null,
      "postalCode": null,
      "preferredLanguage": "ja-JP",
      "provisionedPlans": [
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "MicrosoftCommunicationsOnline"
        },
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "exchange"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "exchange"
        },
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "MicrosoftOffice"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        },
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        }
      ],
      "provisioningErrors": [
        
      ],
      "proxyAddresses": [
        "SMTP:demouser01@o365demo01.onmicrosoft.com"
      ],
      "state": null,
      "streetAddress": null,
      "surname": "Demo",
      "telephoneNumber": null,
      "usageLocation": "JP",
      "userPrincipalName": "demouser01@o365demo01.onmicrosoft.com"
    },
    {
      "odata.type": "Microsoft.WindowsAzure.ActiveDirectory.User",
      "objectType": "User",
      "objectId": "234ddd85-8988-4335-a4a5-5b63fe5652b8",
      "accountEnabled": true,
      "assignedLicenses": [
        {
          "disabledPlans": [
            
          ],
          "skuId": "3b555118-da6a-4418-894f-7df1e2096870"
        }
      ],
      "assignedPlans": [
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Enabled",
          "service": "SharePoint",
          "servicePlanId": "c7699d2e-19aa-44de-8edf-1736da088ca1"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Enabled",
          "service": "exchange",
          "servicePlanId": "9aaf7827-d63c-4b61-89c3-182f06f82e5c"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Deleted",
          "service": "RMSOnline",
          "servicePlanId": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Deleted",
          "service": "MicrosoftOffice",
          "servicePlanId": "43de0ff5-c92c-492b-9116-175376d08c38"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Enabled",
          "service": "MicrosoftCommunicationsOnline",
          "servicePlanId": "0feaeb32-d00e-4d66-bd5a-43b5b83db82c"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Enabled",
          "service": "SharePoint",
          "servicePlanId": "e95bec33-7c88-4a70-8e19-b10bd9d0c014"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Deleted",
          "service": "SharePoint",
          "servicePlanId": "5dbe027f-2339-4123-9542-606e4d348a72"
        },
        {
          "assignedTimestamp": "2015-04-06T09:50:09Z",
          "capabilityStatus": "Deleted",
          "service": "exchange",
          "servicePlanId": "efb87545-963c-4e0d-99df-69c6916d9eb0"
        }
      ],
      "city": "Demo-City",
      "country": null,
      "department": null,
      "dirSyncEnabled": null,
      "displayName": "Matsuzaki Tsuyoshi",
      "facsimileTelephoneNumber": null,
      "givenName": "Tsuyoshi",
      "jobTitle": null,
      "lastDirSyncTime": null,
      "mail": "tsmatsuz@o365demo01.onmicrosoft.com",
      "mailNickname": "tsmatsuz",
      "mobile": "+81 9011110000",
      "otherMails": [
        "tsmatsuz_demo@hotmail.com"
      ],
      "passwordPolicies": "DisablePasswordExpiration",
      "passwordProfile": null,
      "physicalDeliveryOfficeName": null,
      "postalCode": "1110000",
      "preferredLanguage": "ja-JP",
      "provisionedPlans": [
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "exchange"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "exchange"
        },
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "MicrosoftOffice"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "MicrosoftCommunicationsOnline"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        },
        {
          "capabilityStatus": "Deleted",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        },
        {
          "capabilityStatus": "Enabled",
          "provisioningStatus": "Success",
          "service": "SharePoint"
        }
      ],
      "provisioningErrors": [
        
      ],
      "proxyAddresses": [
        "SMTP:tsmatsuz@o365demo01.onmicrosoft.com"
      ],
      "state": "Tokyo",
      "streetAddress": "1-1-1, Demo-Street Demo-City",
      "surname": "Matsuzaki",
      "telephoneNumber": "09011112222",
      "usageLocation": "JP",
      "userPrincipalName": "tsmatsuz@o365demo01.onmicrosoft.com"
    }
  ]
}

OData に対応しているので、下記の通り Query (検索) も可能です。(下記は、Mail Address が「tsmatsuz@o365demo01.onmicrosoft.com」の User を取得しています。)
Azure Active Directory Graph で使用可能なクエリーの内容は「MSDN : Azure AD Graph Common Queries」に記載されています。

GET https://graph.windows.net/o365demo01.onmicrosoft.com/users()?$filter=mail%20eq%20'tsmatsuz%40o365demo01.onmicrosoft.com'
Authorization: Bearer {access token}
api-version: 1.0

Graph API を使って更新処理 (追加、変更、削除) も可能です。
下記では、ObjectId が 234ddd85-8988-4335-a4a5-5b63fe5652b8 の User の DisplayName のみを変更 (Update) しています。

MERGE https://graph.windows.net/o365demo01.onmicrosoft.com/directoryObjects/234ddd85-8988-4335-a4a5-5b63fe5652b8/Microsoft.DirectoryServices.User
api-version: 1.0
Content-Type: application/json;odata=minimalmetadata
Accept: application/json;odata=minimalmetadata
Authorization: Bearer {access token}

{
  "odata.type":"Microsoft.DirectoryServices.User",
  "displayName":"Tsuyoshi Matsuzaki"
}

また、Graph API を使用して、ユーザー情報などの差分 (変更) の追跡が可能です。
例えば、差分情報の取得 (Differential Query) を使って、独自のユーザー管理システムとの (User 情報などの) 同期処理 (Sync) もプログラミングできます。(Office 365 Identity program で Certify された WS-Federation プロバイダーや Shibboleth 以外のカスタム・アプリケーションで同期をおこなう場合には、この方法を使用します。)

補足 : プログラミングの概念は、一般の Sync のプログラミングに類似しており、SkipToken という変更ポイントを管理するキーを生成し、このキーを元に、その時点から現在までの差分を取得できます。詳細は「Azure AD Graph API Differential Query」を参照してください。

この他に、Users エンティティなどの Store の拡張 (プロパティの追加など) や、Permission 管理、Application Role 管理などの細かな管理タスクも実行できます。(処理内容によっては、最新の api-version を使用してください。)

Graph Explorer を使用すると、Browser を使用して Graph API の Request / Response を簡単に確認できます。
また、「MSDN : Azure Graph REST APIs Reference」で Graph API を interactive に確認可能になりましたので、同様に Request / Response を簡単に確認できます。(2015/05/01 追記)

 

Azure Active Directory Graph Client Library

.NET (C# など) を使用している方は、NuGet から Azure Active Directory Graph Client Library (Microsoft.Azure.ActiveDirectory.GraphClient) を取得してプログラミングできます。(上述の ADAL と共に使用します。)

例えば、下記は、Mail Address が「tsmatsuz@o365demo01.onmicrosoft.com」の User を取得して、その User の DisplayName を Update するサンプル コードです。

...
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.ActiveDirectory.GraphClient;
...

private async void button1_Click(object sender, EventArgs e)
{
  Uri serviceRoot = new Uri(
    new Uri("https://graph.windows.net"),
    "o365demo01.onmicrosoft.com");
  ActiveDirectoryClient cl = new ActiveDirectoryClient(
    serviceRoot,
    async () => await GetToken());
  var u = await cl.Users.Where(
    user => user.Mail.Equals("tsmatsuz@o365demo01.onmicrosoft.com"))
    .ExecuteSingleAsync();
  u.DisplayName = "Tsuyoshi Matsuzaki";
  await u.UpdateAsync();
  MessageBox.Show("Finished !");
}

private Task<string> GetToken()
{
  AuthenticationContext context =
    new AuthenticationContext("https://login.microsoftonline.com/o365demo01.onmicrosoft.com");
  AuthenticationResult assertion = context.AcquireToken(
    "https://graph.windows.net",
    "39ff4d28-9cba-448c-acae-612705e54257",  // client id
    new Uri("https://localhost/sampleapp"));
  return Task.Factory.StartNew<string>(() => assertion.AccessToken);
}
. . .

 

 

※ 変更履歴 :

2015/03/19  エンドポイントを https://login.windows.net から https://login.microsoftonline.com (新エンドポイント) に変更しました

2015/04/10  最新の情報に Update。また、Application スコープの認証方法についての解説は「Azure AD : Backend Server-Side アプリの開発 (Deamon, Service など)」を参照するように変更

2015/05/01  Interactive Graph API reference の記述を追加

2015/11/19  Microsoft Graph API について追記

2017/05/26  画面を新ポータル (https://portal.azure.com) に変更

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s