かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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になります。