HoloLens でパスワード入力せずに Azure AD の認証トークンを取得する

HoloLens で業務系のアプリを作っていると、Azure AD アカウントで認証して、認証トークンを持ってバックエンドの API にリクエストを投げたいケースがある。 そのときに問題になるのが ID/PW を入力する際の HoloLens の仮想キーボードの打ちにくさで、慣れているユーザであっても、あのキーボードで毎回ログイン操作を行うというのは苦痛である。

改善策はいろいろ考えられるが、その一つとして、UWP の WebAccountManager API を使う方法がある。 この方法を使うと、事前に OS で Azure AD アカウント(組織アカウント)を設定していれば、ID/PW 入力なしに認証トークンを取得することができる。

※逆に言うとこの方法では、デバイスが第三者に渡ってしまうとパスワード入力なしに認証されてしまうことになる。デバイスとユーザが一対一で紐づいていることが前提となり、また端末の盗難等も考慮していないので、実案件ではセキュリティ要件に応じて利用可否を判断すること。

1. WebAccountManager API の説明

詳細は以下のドキュメントを参照してもらうとして、簡単に言うと、Windows.UI.ApplicationSettings.AccountsSettingsPane という認証画面が用意されているので、それを呼び出すことで、Azure AD や MSA、その他 Facebook 等の外部認証基盤(IdP)への認証を行うことができるというものだ。

docs.microsoft.com

実装にあたっては、MS が提供している以下のサンプルアプリが参考になる。UWP なので、そのまま HoloLens で試してみることもできる。(ただし後述する Store や Azure AD への登録は必要)

github.com

1.1 WebAccountManager API の実装流れ

先に認証処理実装の簡単な流れを説明する。 API の使い方は単純になっていて、認証画面が開く直前のイベントに以下の処理を登録してから、認証画面を開く(AccountsSettingsPane.Show())だけで良い。

  1. どの認証基盤を使うかの登録(今回は Azure AD のみ登録する)
  2. ユーザが認証画面でアカウントを選択した際に行う処理
// AccountsSettingsPane が開く直前に呼ばれるイベントハンドラを登録
AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested += OnAccountCommandsRequested;

// AccountsSettingsPane を開く
AccountsSettingsPane.Show()`

イベントハンドラOnAccountCommandsRequested)の中身は以下のような感じになる。 なお、FindAccountProviderAsync()の第2引数には、Azure AD の場合は "organizations", MSA の場合には "consumers" という文字列を渡すように決まっているので注意する。今回は Azure AD のみ利用するので、"organizations" を渡している。

private async void OnAccountCommandsRequested(AccountsSettingsPane sender, AccountsSettingsPaneCommandsRequestedEventArgs e) {
    AccountsSettingsPaneEventDeferral deferral = e.GetDeferral();

    // 1. Azure AD 用の IdP を取得する
    // 第2引数には、Azure AD の場合は "organizations", MSA の場合には "consumers" という文字列を渡す
    WebAccountProvider provider = await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "organizations");

    // 2. ユーザが認証画面でアカウントを選択した際のイベントハンドラを登録する
    WebAccountProviderCommand providerCommand = new WebAccountProviderCommand(provider, WebAccountProviderCommandInvoked);
    e.WebAccountProviderCommands.Add(providerCommand);

    deferral.Complete();
}

ユーザがアカウントを選択した際のイベントハンドラWebAccountProviderCommandInvoked)で、実際にトークンを要求する処理を行う。

private async void WebAccountProviderCommandInvoked(WebAccountProviderCommand command) {
    try {
        WebTokenRequest webTokenRequest = new WebTokenRequest(command.WebAccountProvider, "", "<事前に登録したアプリの Client ID>");

        // アクセスしたいリソースを登録
        webTokenRequest.Properties.Add("resource", "https://graph.windows.net");

        // トークンの要求を実行
        WebTokenRequestResult webTokenRequestResult = await WebAuthenticationCoreManager.RequestTokenAsync(webTokenRequest);
        if(webTokenRequestResult.ResponseStatus == WebTokenRequestStatus.Success) {
            // 成功した場合、トークンやユーザ名が取得できる
            Console.WriteLine(webTokenRequestResult.ResponseData[0].WebAccount.Token);
        } else {
            // 失敗した場合
        }
    }
    catch(Exception ex) {
        // 失敗した場合
    }
}

2. 実装する

以上を踏まえて、実際に Unity Plugin として実装を行っていく。 ただし、実装の前にいくつか事前準備が必要となる。

2.1 事前準備

WebAccountManager API を使った認証を行うには、事前準備として以下を行う必要がある。

  • Microsoft Store へのアプリ登録(パッケージ SID の取得)
  • Azure AD へのアプリ登録

2.1.1 Microsoft Store へのアプリ登録

以下のページから、開発者アカウントを登録する。(登録には $19 必要。ただし更新不要なので一回のみの支払いでOK)

アプリ開発者として登録 – Windows アプリ開発

アカウントを登録したら、開発者ダッシュボードの「概要」タブから、アプリを新規作成して、名前を予約する。

名前の予約によるアプリの作成 - UWP app developer | Microsoft Docs

名前を予約できると、アプリのパッケージ SID が確定するので、ダッシュボードのアプリページの「アプリ管理」→「アプリID」から確認しておく。

f:id:nosunosu:20180622114247p:plain

2.1.2 Azure AD へのアプリ登録

Azure Portal の Azure AD 管理画面からアプリを登録する。 アプリの種類は「ネイティブ」とする。 リダイレクト URI には、以下の 2 つを登録する。

  • ms-appx-web://Microsoft.AAD.BrokerPlugIn/<パッケージ SID>
  • ms-appx-web://Microsoft.AAD.BrokerPlugIn/<作成した Azure AD アプリのアプリケーション ID>

f:id:nosunosu:20180622114444p:plain

2.2 Unity Plugin を実装

前述した WebAccountManager API を使った認証を、今回は Unity Plugin として切り出して開発し、Unity C# Script から呼び出す形で利用する。 Unity Plugin の実装方法については、以下のページが参考になる。

satoshi-maemoto.hatenablog.com

blog.okazuki.jp

詳細は割愛するが、全く同じ名前空間で、認証を実装する本物の dll と、UWP の API を利用しない Unity Editor 用のダミーの dll の 2 つを作成するところがポイント。

今回実装したサンプルの Plugin は以下に置いている。

github.com

2.3 実装した Plugin を Unity に登録して呼び出す

上記の Plugin を Release ビルドして 2 つの dll を生成したら、それを Unity にインポートし、それぞれ以下のように設定する。

UWP 用 dll

f:id:nosunosu:20180622135207p:plain

Unity 用ダミー dll

f:id:nosunosu:20180622135149p:plain

登録できたら、あとは Unity C# Script 側で、好きな契機で Plugin の認証メソッドを呼べば良い。 ただし、呼び出す際には以下のように UnityEngine.WSA.Application.InvokeOnUIThread() を使って UI スレッドで実行する必要があることに注意する。

UnityEngine.WSA.Application.InvokeOnUIThread(() => {
    // Plugin の呼び出し
    var authClient = new WebAuthClient(ClientId, Resource);

    // 引数として、取得したトークン(またはエラー内容)を受け取るコールバックメソッドを渡す
    authClient.GetAzureADToken(OnAuthCompleted, OnAuthFailed);
}, true);

Unity 側の実装が終わったら、UWP ソリューションをビルドして、Visual Studio でソリューションを開く。 その後、Visual Studio で UWP アプリをビルドする前に、事前に Store で予約した名前と紐づける必要がある。 プロジェクトの右クリックメニューから、GUI で紐づけを行うことができる。

f:id:nosunosu:20180622142312p:plain f:id:nosunosu:20180622142336p:plain

3. 使ってみる

Store との紐づけが終わったら、Ctrl + F5 で実機実行してみる。 HoloLens で以下のような画面が開くので、アカウントを選択して「Continue」をタップすると、トークンが正しく取得されたことがわかる。

f:id:nosunosu:20180622143258j:plain

f:id:nosunosu:20180622143328j:plain