チュートリアル: マルチ テナント型のサーバー間認証の使用

 

公開日: 2017年1月

対象: Dynamics 365 (online)

このチュートリアルは、Microsoft Visual Studio 2015 MVC Web アプリケーション テンプレートを使用して、Microsoft Dynamics 365 用 2016 年 12 月の更新プログラム (オンライン) テナントに接続するマルチ テナント型の Web アプリケーションを作成する方法について説明します。

要件

  • Web 開発者ツールがインストールされた Visual Studio 2015

  • Azure Active Directory (Azure AD) テナントと関連付けられた Microsoft Dynamics 365 用 2016 年 12 月の更新プログラム (オンライン) テナント。

  • 異なる Azure AD テナントに関連付けられている 2 番目の Microsoft Dynamics 365 用 2016 年 12 月の更新プログラム (オンライン) テナント。 このテナントはアプリケーションのサブスクライバーを表します。 これは、Microsoft Dynamics 365 用 2016 年 12 月の更新プログラム (オンライン) サブスクリプションの試用版です。

このチュートリアルの目標

このチュートリアルを完了すると、WhoAmIRequest Class を使用する MVC Web アプリケーションが存在するようになり、それを使用して Dynamics 365 (オンライン) テナントに接続するために使用するアプリケーションを使用するユーザーに関するデータを取得します。

アプリを正常に実行すると、[サインイン] コマンドが右上隅に表示されます。

The sign in command in the app

[サインイン] コマンドをクリックすると、Azure AD に移行され、資格情報を入力します。

サインイン後、[WhoAmI] コマンドが表示されます。

The WhoAmI command

[WhoAmI] をクリックすると、次が表示されます。

Results of a WhoAmI request

Dynamics 365 テナントをクエリすると WhoAmI メッセージから返される結果が、現在使用しているユーザー アカウントよりも Web アプリケーションに対して構成した、指定されたアプリケーション ユーザー アカウントを参照することが分かります。

Azure AD テナントを確認する

開始する前に、Office 365 管理センターhttps://portal.office.com に接続し、[管理センター] ドロップダウンで、Dynamics 365 と [Azure AD] の両方が表示されていることを確認します。

Admin Centers with Azure Active Directory and Dynamics 365

Azure AD サブスクリプションが Dynamics 365 サブスクリプションに関連付けられていない場合、アプリケーションには Dynamics 365 データにアクセスするための特権が付与されません。

このオプションが表示されない場合、「無料で Azure Active Directory のサブスクリプションに登録する」を参照して、Azure AD サブスクリプションを取得するために登録する方法に関する情報を取得してください。

既に Azure サブスクリプションがあるが、Microsoft Office 365 アカウントに関連付けられていない場合、「Azure AD を使用して Office 365 アカウントを関連付ける」を参照してアプリの作成および管理をしてください。

MVC Web アプリケーションを作成する

Visual Studio 2015 を使用して、新しい MVC Web アプリケーションの作成および Azure AD テナントへの登録ができます。

  1. [Visual Studio 2015] を開きます。

  2. サインインするときに使用する Microsoft アカウント が、アプリケーションを登録するために使用する Azure AD テナントへのアクセスに使用する ID と同じことを確認します。

  3. [新しいプロジェクト ] をクリックして、[ .NET Framework 4.6.1] を選択し、[ASP.NET Web アプリケーション] を選択してください。

    [OK] をクリックし、新しい ASP.NET プロジェクト ダイアログで、[MVC] を選択します。

  4. [認証の変更] ボタンをクリックして、ダイアログで [職場または学校のアカウント] を選択します。

  5. ドロップダウンで、[クラウド – 複数組織] を選択します。

    ASP.NET MVC Change Authentication Dialog

  6. [OK] をクリックして、プロジェクトの初期化を完了します。

    注意

    Visual Studio プロジェクトをこの方法で作成すると、Azure AD テナントを使用してアプリケーションが登録され、Web.Config appSettings に次のキーが追加されます。

    <add key="ida:ClientId" value="baee6b74-3c39-4c04-bfa5-4414f3dd1c26" />
    <add key="ida:AADInstance" value="https://login.microsoftonline.com/" />
    <add key="ida:ClientSecret" value="HyPjzuRCbIl/7VUJ2+vG/+Gia6t1+5y4dvtKAcyztL4=" /> 
    

Azure ADV にアプリケーションを登録する

MVC Web アプリケーションを作成する の手順通りに作業した場合、Visual Studio に作成した Web アプリケーション プロジェクトが Azure AD アプリケーションに既にに登録されているはずです。 しかし Azure AD ポータル内でさらに 1 つの手順を実行する必要があります。

  1. https://portal.azure.com に移動し、[Azure Active Directory] を選択します。

  2. [アプリ登録] をクリックして、Visual Studio を使用して作成したアプリケーションを検索します。 [全般] 領域で、プロパティを確認します。

    Application registration data in Azure Active Directory

  3. [アプリケーション ID] プロパティが、Web.Config appSettings に追加した ClientId 値と一致していることを確認します。

  4. [ホーム ページ URL] 値は Visual Studio プロジェクトの SSL URL プロパティと一致していて、localhost URL (例えば https://localhost:44392/) に導く必要があります。

    注意

    実際にアプリケーションを公開した時に変更する必要があります。 しかしデバッグ用に正しい localhost 値を設定する必要があります。

  5. Dynamics 365 データにアクセスする特権をアプリケーションに与える必要があります。 [API アクセス] 領域で [必要なアクセス許可] をクリックします。 Windows Azure Active Directory に対するアクセスが許可が既に与えられていることが確認できます。

  6. [追加] をクリックし、次に [API の選択] をクリックします。 一覧で Dynamics 365 を選択し、次に [選択] ボタンをクリックします。

  7. [アクセス許可の選択] で、[組織のユーザーとして Dynamics 365 にアクセスする] を選択します。 次に、[選択] ボタンをクリックします。

  8. これらのアクセス許可を追加するには、[完了] をクリックします。 完了後にアクセス許可が反映されていることを確認します。

    Dynamics 365 permissions applied to application in Azure Active Directory

  9. [API アクセス] 領域で、[キー] 値が追加されていることを確認します。 アプリケーションが作成された後には [キー] 値は Azure ポータルに表示されませんが、この値は Web.Config appSettings に ClientSecret として追加されました。

アプリケーション ユーザーの作成

[Dynamics 365] アプリケーション ユーザーを手動で作成します の手順を使用して、Web.Config の ClientId 値とも同じ [アプリケーション ID] 値を使用したアプリケーション ユーザーを、アプリケーションの登録から作成します。

アセンブリの追加

次の NuGet パッケージをプロジェクトに追加する

パッケージ

[バージョン]

Microsoft.CrmSdk.CoreAssemblies

最新バージョン

Microsoft.IdentityModel.Clients.ActiveDirectory

2.22.302111727

Microsoft.IdentityModel.Tokens

5.0.0

Microsoft.Azure.ActiveDirectory.GraphClient

2.1.0

注意

Microsoft.IdentityModel.Clients.ActiveDirectory アセンブリを最新のバージョンに更新しないでください。 これらのアセンブリのバージョン 3.x は、Microsoft.CrmSdk.CoreAssemblies が依存するインターフェイスを変更しました。

管理 NuGet パッケージの詳細については、「NuGet ドキュメント: UI を使用した NuGet パッケージの管理」を参照してください。

コードの変更を MVC テンプレートに適用する

次のコードは、Dynamics 365WhoAmI メッセージを使用するための基本的な機能を提供し、アプリケーションのユーザー アカウントがアプリケーションによって使用されていることを確認します。

Web.config

appSettings に次のキーを追加します。

<add key="ida:OrganizationHostName" value="https://{0}.crm.dynamics.com" /> 

ida:OrganizationHostName 文字列により、サブスクライバーの Dynamics 365 オンライン組織名がプレースホルダーに追加されるので、正しいサービスがアクセスされます。

<add key="owin:appStartup" value="<your app namespace>.Startup" />

owin:appStartup 文字列は、OWIN ミドルウェアが Startup クラスをこのプロジェクトで使用していることを確認します。 それ以外の場合は、次のエラーが表示されます。

- No assembly found containing an OwinStartupAttribute.
- No assembly found containing a Startup or [AssemblyName].Startup class.

詳細については、「ASP.NET: OWIN スタートアップ クラスの検出」を参照してください。

Controllers/HomeController.cs

AllowAnonymous の装飾者を Index アクションに追加します。 これにより、認証なしで既存のページにアクセスできます。

using System.Web.Mvc;

namespace SampleApp.Controllers
{
    [Authorize]
    public class HomeController : Controller
    {
        [AllowAnonymous]
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

注意

Web アプリケーションまたはサービスで、匿名アクセスを許可することは予期されていません。 単純化するために、匿名アクセスがここで使用されます。 アプリケーションに対するアクセス制御は、このチュートリアルではスコープ外となります。

Views/Shared/_Layout.cshtml

認証されたユーザーが [WhoAmI] コマンド リンクを表示するには、このファイルを編集する必要があります。

navbar-collapse collapse クラスを使用する div 要素を見つけて、次のコードを含むように編集します。

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
     <li>@Html.ActionLink("Home", "Index", "Home")</li>
     <li>@Html.ActionLink("About", "About", "Home")</li>
     <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
     @if (Request.IsAuthenticated)
     {
         <li>@Html.ActionLink("WhoAmI", "Index", "CrmSdk")</li>
     }
    </ul>

    @Html.Partial("_LoginPartial")
   </div>

App_Start/Startup.Auth.cs

次の変更は、新しいテナントがアプリケーションにログインしたときに、同意したフレームワークを呼び出します。

public partial class Startup
 {
  private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
  private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
  //Not used   
  //private string graphResourceID = "https://graph.windows.net";    
  private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
  private string authority = aadInstance + "common";
  private ApplicationDbContext db = new ApplicationDbContext();

  //Added
  private string OrganizationHostName = ConfigurationManager.AppSettings["ida:OrganizationHostName"];

  public void ConfigureAuth(IAppBuilder app)
  {

   app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

   app.UseCookieAuthentication(new CookieAuthenticationOptions { });

   app.UseOpenIdConnectAuthentication(
       new OpenIdConnectAuthenticationOptions
       {
        ClientId = clientId,
        Authority = authority,
        TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
        {
         /*
         instead of using the default validation 
         (validating against a single issuer value, as we do in line of business apps), 
         we inject our own multitenant validation logic
         */
         ValidateIssuer = false,
        },
        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
         SecurityTokenValidated = (context) =>
                  {
                   return Task.FromResult(0);
                  },
         AuthorizationCodeReceived = (context) =>
                  {
                   var code = context.Code;

                   ClientCredential credential = new ClientCredential(clientId, appKey);
                   string tenantID = context
                    .AuthenticationTicket
                    .Identity
                    .FindFirst("https://schemas.microsoft.com/identity/claims/tenantid")
                    .Value;

                   /* Not used
                  string signedInUserID = context
                     .AuthenticationTicket
                     .Identity
                     .FindFirst(ClaimTypes.NameIdentifier)
                     .Value;  
                     */

                   //Added
                   var resource = string.Format(OrganizationHostName, '*');
                   //Added
                   Uri returnUri = new Uri(
                    HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)
                    );

                   /* Changed below
                    AuthenticationContext authContext = 
                    new AuthenticationContext(
                     aadInstance + tenantID, 
                     new ADALTokenCache(signedInUserID)
                     );
                    */
                   //Changed version
                   AuthenticationContext authContext =
                   new AuthenticationContext(aadInstance + tenantID);

                   /* Changed below
                   AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                       code, 
                       new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), 
                       credential, 
                       graphResourceID);
                   */
                   //Changed version
                   AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                       code,
                       new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
                       credential,
                       resource);

                   return Task.FromResult(0);
                  },
         AuthenticationFailed = (context) =>
                  {
                   context.OwinContext.Response.Redirect("/Home/Error");
                   context.HandleResponse(); // Suppress the exception
                   return Task.FromResult(0);
                  }
        }
       });

  }
 }

Add Controllers/CrmSdkController

次の CrmSdkController.cs を Controllers フォルダーに追加します。 このコードによって、WhoAmI メッセージを実行します。

  1. Controllers フォルダーを右クリックして、[追加] > [コントローラー…] を選択します。

  2. [Scaffold の追加] ダイアログで、[MVC5 コントローラー – 空] を選択します。

  3. [追加] をクリックします。

  4. アプリの名前と <Your app namespace> の代わりに、次のコードを貼り付けます。

using Microsoft.IdentityModel.Clients.ActiveDirectory; 
using Microsoft.Xrm.Sdk; 
using Microsoft.Xrm.Sdk.WebServiceClient; 
using System; using System.Configuration; 
using System.Linq; 
using System.Security.Claims; 
using System.Web.Mvc;

namespace <Your app namespace>
{
 [Authorize]
 public class CrmSdkController : Controller
    {

  private string clientId = 
   ConfigurationManager.AppSettings["ida:ClientId"];
  private string authority = 
   ConfigurationManager.AppSettings["ida:AADInstance"] + "common";
  private string aadInstance = 
   ConfigurationManager.AppSettings["ida:AADInstance"];
  private string OrganizationHostName = 
   ConfigurationManager.AppSettings["ida:OrganizationHostName"];
  private string appKey = 
   ConfigurationManager.AppSettings["ida:ClientSecret"];


  // GET: CrmSdk
  public ActionResult Index()
  {
   string tenantID = ClaimsPrincipal
    .Current
    .FindFirst("https://schemas.microsoft.com/identity/claims/tenantid")
    .Value;
   // Clean organization name from user logged
   string organizationName = User.Identity.Name.Substring(
    User.Identity.Name.IndexOf('@') + 1, 
    User.Identity.Name.IndexOf('.') - (User.Identity.Name.IndexOf('@') + 1)
    );
   //string crmResourceId = "https://[orgname].crm.microsoftonline.com";
   var resource = string.Format(OrganizationHostName, organizationName);
   // Request a token using application credentials
   ClientCredential clientcred = new ClientCredential(clientId, appKey);
   AuthenticationContext authenticationContext = 
    new AuthenticationContext(aadInstance + tenantID);
   AuthenticationResult authenticationResult = 
    authenticationContext.AcquireToken(resource, clientcred);
   var requestedToken = authenticationResult.AccessToken;
   // Invoke SDK using using the requested token
   using (var sdkService =
    new OrganizationWebProxyClient(
     GetServiceUrl(organizationName), false)
     )
   {
    sdkService.HeaderToken = requestedToken;
    OrganizationRequest request = new OrganizationRequest() {
     RequestName = "WhoAmI"
    };
    OrganizationResponse response = sdkService.Execute(request);
    return View((object)string.Join(",", response.Results.ToList()));
   }
  }

  private Uri GetServiceUrl(string organizationName)
  {
   var organizationUrl = new Uri(
    string.Format(OrganizationHostName, organizationName)
    );
   return new Uri(
    organizationUrl + 
    @"/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"
);
  }
 }
}

Views/CrmSdk

Index という名前の新しいビューを追加します。

  1. CrmSdk フォルダーを右クリックして、[追加] > [ビュー…] を選択します。

  2. [ビューの追加] ダイアログで、次の値を入力します。

    MVC Add View Dialog

  3. [追加] をクリックします。

  4. 次のコードで生成するコードを置換します。

    @model string
    @{
     ViewBag.Title = "SDK Connect";
    }
    
    
    <h2>@ViewBag.Title.</h2>
    
    <p>Connected and executed sdk command WhoAmI.</p>
    
    <p>Value: @Model</p>
    

アプリケーションのデバッグ

F5 を押してアプリケーションをデバッグすると、SSL を使用して localhost にアクセスしている証明書が信頼されていないというエラーが発生する場合があります。Visual Studio および IIS Express におけるこの問題を解決するためのリンクを次に示します。

注意

この手順では、Azure AD テナントおよびそれに関連する Dynamics 365 テナントに関連付けられている Microsoft アカウント を使用します。 実際には、これはマルチテナント型のシナリオを示していません。 次の手順でこれを実行します。 この手順では、追加の複雑なテストにより実際のマルチテナント型機能を試す前に、コードが実際に動作することを確認します。

このチュートリアルの目標 に説明されている手順を参照して、アプリケーションを検証してください。

この時点で、アプリケーション ユーザー アカウントが使用されたことを確認できます。 これを確認する簡単な方法は Dynamics 365 Web API を使用することです。 アプリケーションの UserId 値の代わりに、異なるタブまたはウィンドウに、次の URL を入力します。

[Organization URI]/api/data/v8.2/systemusers(<UserId value>)?$select=fullname

JSON の反応は次のようになります。 アプリケーションにサインインするために使用する Dynamics 365 ユーザーよりも、アプリケーション ユーザーの作成 の手順で作成したアプリケーション ユーザーの fullname 値になることに注意してください。

    {
        "@odata.context": "[Organization Uri]/api/data/v8.2/$metadata#systemusers(fullname)/$entity",
        "@odata.etag": "W/\"603849\"",
        "fullname": "S2S User",
        "systemuserid": "31914b34-be8d-e611-80d8-00155d892ddc",
        "ownerid": "31914b34-be8d-e611-80d8-00155d892ddc"
    }

テスト用サブスクライバーの構成

アプリケーションが動作することを確認したので、異なる Dynamics 365 (オンライン) テナントの接続をテストする時間です。 異なる Dynamics 365 (オンライン) 組織を使用して、以下の手順を実行する必要があります。

サブスクライブ テナントからの承認を付与する

承認を付与するには、Azure AD アドミンとしてログイン中に次の手順を実行してください。

  1. アプリケーションをデバッグ中に、別の InPrivate または incognito ウィンドウを開きます。

  2. ウィンドウの住所フィールドに、例えば https://localhost:44392/ のようなアプリの URL を入力します。

  3. [サインイン] ボタンをクリックすると、承認を付与するよう求められます。

    Azure Active Directory consent form

承認を付与した後に、アプリに戻りますが、まだ使用できません。 この時点で [WhoAmI] をクリックすると、次の例外が予期できます。

System.ServiceModel.Security.MessageSecurityException
HResult=-2146233087
  Message=The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Bearer authorization_uri=https://login.windows.net/4baaeaaf-2771-4583-99eb-7c7e39aa1e74/oauth2/authorize, resource_id=https://<org name>.crm.dynamics.com/'.
InnerException.Message =The remote server returned an error: (401) Unauthorized.

承認を付与することにより、Azure AD テナントのアプリケーションが、サブスクリプションのアクティブ ディレクトリ テナントのアプリケーションに追加されます。

サブスクライバー テナントにユーザー定義のセキュリティ ロールを作成する

作成する必要があるアプリケーション ユーザーは、特権を定義するユーザー定義のセキュリティ ロールに関連付けられている必要があります。 この手動のテストの手順では、まずユーザー定義のセキュリティ ロールを手動で作成する必要があります。詳細:TechNet: セキュリティ ロールの作成または編集

注意

アプリケーション ユーザーは既定の Dynamics 365 セキュリティ ロールの 1 つと関連付けることができません。 アプリケーション ユーザーに関連付ける、ユーザー定義のセキュリティ ロールを作成する必要があります。

サブスクライバー アプリケーション ユーザーを作成する。

このチュートリアルの目的として、アプリケーションのユーザーを手動で作成して、異なるテナントからの接続を検証します。 実際のサブスクライバーに展開する場合は、これを自動化します。詳細:アプリケーション ユーザーを展開するメソッドを準備します

アプリケーション ユーザーの作成 の開発組織で使用したのと同じ値を手動で使用してアプリケーション ユーザーを作成します。 最初に承認を付与する手順を完成している必要がある場合は例外です。 ユーザーを保存する場合、[アプリケーション ID URI] と [Azure AD オブジェクト ID] 値が設定されます。 最初に承認を付与していない場合、ユーザーを保存することはできません。

最初に、以前のステップで追加したユーザー定義のセキュリティ ロールをアプリケーション ユーザーに関連付けます。

サブスクライバー接続をテストする。

他の Dynamics 365 テナントの資格情報を使用する場合を除いて、アプリケーションのデバッグ の手順を繰り返します。

関連項目

マルチ テナント型でのサーバー間認証の使用
単一テナント型のサーバー間認証の使用
サーバー間 (S2S) の認証を使用して Web アプリケーションを作成する
Microsoft Dynamics 365 に接続

Microsoft Dynamics 365

© 2017 Microsoft. All rights reserved. 著作権