かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure AD対応のマルチテナントアプリケーションを作ってみよう

以下のサイトにサンプルがあるので、それに沿ってやっていきます。

github.com

Azure ADのディレクトリを作る

というわけでさくっとクラシックポータルからAzure ADのディレクトリを作ります。 ここでは、okazukimultitenanttestという名前で作りました。

アプリケーションも作りましょう。multitenanttestappという名前でWebアプリケーションとして作っておきます。 サイオンオンURLと応答URLを、以下のような感じにします。

https://これから作るWebAppsの名前.azurewebsites.net/

アプリケーション ID/URIを以下のようにします。

https://<テナント名>.onmicrosoft.com/なんか適当

テナント名は今回はokazukimultitenanttestなのでhttps://okazukimultitenanttest.onmicrosoft.com/multitenantappみたいにしました。

アプリケーションの追加から、Microsoft Graphを追加してデリゲートされたアクセス許可にSign in and read user profileを選択します。

そして、キーを生成してメモっておきます。

アプリの作成

空のASP.NET Webアプリケーションを作ります。MVCにチェックを入れておきましょう。 クラウドにホストするを選んでAzureのWeb appsにデプロイできるようにしておきます。(後からでもできる)

ControllersフォルダにHomeControllerを作ります。Indexに対してViewも作っておきましょう。ここら辺は、Indexアクションを右クリックしてビューを追加を選ぶと楽です。

NuGetの追加

NuGetを使って以下のパッケージをインストールします。

  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security.OpenIdConnect
  • Microsoft.Owin.Security.Cookies

Startup.csの追加

OWINのスタートアップのクラスを作ります。Visual StudioにOWIN Stgartupクラスのテンプレートがあるので、そこから選んで作りましょう。

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.IdentityModel.Protocols;
using System.Configuration;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.IdentityModel.Tokens;
using System.IdentityModel.Claims;

[assembly: OwinStartup(typeof(MultiTenantApp.Startup))]

namespace MultiTenantApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var clientId = ConfigurationManager.AppSettings["ida:ClientId"];
            var authority = "https://login.microsoftonline.com/common";

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        // 自分でやるのでfalse
                        ValidateIssuer = false,
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        RedirectToIdentityProvider = ctx =>
                        {
                            var appBaseUrl = $"{ctx.Request.Scheme}://{ctx.Request.Host}{ctx.Request.PathBase}";
                            ctx.ProtocolMessage.RedirectUri = appBaseUrl;
                            ctx.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                            return Task.FromResult(0);
                        },
                        SecurityTokenValidated = ctx =>
                        {
                            // ここで本当はテナントIDとかのチェックをする
                            var issuer = ctx.AuthenticationTicket.Identity.FindFirst("iss").Value;
                            var upn = ctx.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
                            var tenantId = ctx.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

                            // 上記値をチェックして不正なテナントやユーザーだったら
                            // SecurityTokenValidationExceptionを投げる

                            return Task.FromResult(0);
                        },
                        AuthenticationFailed = ctx =>
                        {
                            // エラーの時のリダイレクト
                            ctx.OwinContext.Response.Redirect("/Home/Error");
                            ctx.HandleResponse();
                            return Task.FromResult(0);
                        }
                    }
                });
        }
    }
}

画面の作成

サインインして情報を表示するプログラムを書いてみましょう。HomeControllerを以下のように書き換えます。

using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MultiTenantApp.Controllers
{
    [Authorize]
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            var ctx = this.HttpContext.GetOwinContext();
            var issuer = ctx.Authentication.User.FindFirst("iss").Value;
            var upn = ctx.Authentication.User.FindFirst(ClaimTypes.Name).Value;
            var tenantId = ctx.Authentication.User.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

            this.ViewBag.Issuer = issuer;
            this.ViewBag.Upn = upn;
            this.ViewBag.TenantId = tenantId;

            return View();
        }

        public ActionResult SignOut()
        {
            this.HttpContext.GetOwinContext().Authentication.SignOut();
            return Redirect("/Home");
        }
    }
}

Authorize属性をつけている点とOwinContext経由で認証情報を取っているところがポイントです。

Index.cshtmlでは以下のようにViewBagの情報を画面に出しています。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<ul>
    <li>@this.ViewBag.Issuer</li>
    <li>@this.ViewBag.Upn</li>
    <li>@this.ViewBag.TenantId</li>
</ul>

Web.configへの設定の追加

Web.configに設定を追加します。ADにアプリ作ったときに取得できるクライアントIDとキーをappSettingsのida:ClientIdとida:Passwordに設定します。

<add key="ida:ClientId" value="クライアントID" />
<add key="ida:Password" value="パスワード" />

実行して動作確認

デプロイして実行してみると以下のようにログイン画面が出ます。(httpsでアクセスしてね) ログインが求められるのでお手持ちのAzure ADのテナントに登録されているユーザーでログインしてみます。(無ければAzureクラシックポータルから作ってください)

ログインすると、以下のように情報が表示されます。

f:id:okazuki:20170302140747p:plain

テナントIDの取得方法

Azure ADのテナントIDの取得方法ですが、以下のようにして取得できるみたいです。

stackoverflow.com

以下のURLにアクセス

https://login.windows.net/YOURDIRECTORYNAME.onmicrosoft.com/.well-known/openid-configuration

出てきたJSONのauthorization_endpointにあるGUIDっぽいのがテナントIDになります。