Flutter で Google Glass Enterprise Edition 2 のアプリを作る際のメモ

Google Glass Enterprise Edition 2(以下「Glass」)の実機を入手したので、サンプルアプリをいろいろ作ってみている。 Android ネイティブで作ってもいいが、先日 Flutter 2 が出たところだし、せっかくなので Flutter で試してみている。 とりあえず、Tips がいくつかあったのでメモしておく。

アプリアイコンを Glass の Launcher に表示させる

Flutter のプロジェクト作成時の初期状態では、apk をビルドしてインストールしても、Glass のホーム画面である Launcher にはアイコンが表示されない。 アイコンを表示させるためには、Project/android/app/src/main/AndroidManifest.xml に以下の記述を追加する。*1

<manifest ...>
    <application ...>
        <intent-filter>
            <!-- ここから -->
            <category android:name="com.google.android.glass.category.DIRECTORY" />
            <!-- ここまで -->
        </intent-filter>
    </application>
</manifest>

指での操作を Flutter 側で利用する

Glass にはタップ操作できる画面はないので、本体横の平面をタップやスワイプするのが標準操作となる。 Flutter では常に AndroidMainActivity それらの操作を Flutter 側でハンドリングするために、

1) Project/android/app/src/main/AndroidManifest.xml に以下の記述を追加する

<manifest ...>
    <application ...>
            <!-- ここから -->
            <meta-data
                android:name="com.google.android.glass.TouchEnabledApplication"
                android:value="true" />
            <!-- ここまで -->
    </application>
</manifest>

2) ネイティブ側の MainActivity.java でネイティブの GestureDetector を使ったハンドラを記述する。

package dev.nosu.flutter_slide_viewer.flutter_slide_viewer;

import android.os.Handler;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.GestureDetector;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivity;

public class MainActivity extends FlutterActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetector mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mDetector = new GestureDetector(this,this);
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
                           float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
                            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

あとは、Flutter 側で GestureDetector を使ってあげれば良い。 下向きのスワイプをアプリ終了に割り当てるには以下のようにする。(Glass では「戻る」または「アプリ終了」操作に割り当てるのが標準になっている)

class GestureContainer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: GestureDetector(
          onVerticalDragEnd: (details) {
            print("onVerticalDragEnd primaryVelocity:" + details.primaryVelocity.toString());
            if(details.primaryVelocity > 0) {
              SystemNavigator.pop();
            }
          },
          child: ChildWidget()
      ),
    );
  }
}

ほかにもいろいろポイントはありそうなので追って追記する予定。

Elgato Key Light (Air) は REST API から直接操作する方が便利

WFH が続く状況を受けて、勤務先からリモート会議で顔を照らすためのライト(いわゆる「女優ライト」的なもの)が支給された。機種は Elgato Key Light Air というもの。

f:id:nosunosu:20210324004701p:plain
Elgato Key Light Air

専用のスマホアプリから操作するようになっているのだが、スマホアプリは起動が遅くて ON/OFF するだけなのに時間がかかって利便性がいまいち。 調べてみると単純な API が提供されており、以下のように一発 REST リクエストを投げるだけで簡単に操作できることがわかった。

1. GET http://<Elgato の IP address>:9123/elgato/lights で現在の状態を取得

IP アドレスを調べて、GET するだけで現在の設定値が JSON で返ってくる。認証等も不要。例えば以下のような感じ。

$ curl http://192.168.1.226:9123/elgato/lights
# -> {"numberOfLights":1,"lights":[{"on":0,"brightness":11,"temperature":246}]}

※ちなみに、うちの ASUS のルータの接続デバイス一覧では、Elgato Key Light は "Dexatek Technology LTD" というクライアント名で表示されていた

戻り値の JSON の中身の意味は以下のとおり。

設定値 意味
numberOfLights ローカルネットワーク内に存在するライトの数?
lights[i].on ライトの ON/OFF (ON = 0, OFF = 1)
lights[i].brightness 明るさ (0〜100)
lights[i].temperature 色温度 (144〜344、暖色〜寒色)

2. PUT http://<Elgato の IP address>:9123/elgato/lights で状態を変更

Light のスイッチを入れたり、状態を変更する場合には、1. で戻ってきた JSON を適宜編集して PUT するだけ。

$ curl -X PUT \
  -d '{"numberOfLights":1,"lights":[{"on":1,"brightness":11,"temperature":246}]}' \
  http://192.168.1.226:9123/elgato/lights
# -> スイッチON

$ curl -X PUT \
  -d '{"numberOfLights":1,"lights":[{"on":0,"brightness":11,"temperature":246}]}' \
  http://192.168.1.226:9123/elgato/lights
# -> スイッチOFF

時間があったら音声コントロールできるようにしたい。

参考

www.ntpro.nl

AutoMuteUs を GCP にセルフホストするスクリプトを書いた

タイトルのとおりです。3行で言うと

  • AmongUs プレイ中に Discord のチャットをいい感じにオンオフしてくれる AutoMuteUs というボットが GitHub で公開されていてとても便利
  • 公式でホスティングしてくれてるやつは混雑していて利用できないことが多いので、自前でホスティングしたい。かつ Discord 側から到達できる Public IP が必要なのでパブリッククラウドに立てたい
  • 料金抑えるのに立てたり消したりするのが面倒なのでセットアップ用のスクリプトを書いたよ

という感じです。

以下の README.md に従って deploy.sh を実行すればデプロイされます。消したい場合は automuteus という VM を消せば OK です。

github.com

AutoMuteUs 自体簡単に使えるようになっていて、基本はリポジトリを clone して docker-compose up するだけなんですが、とはいえ .env を毎回編集したりしなくていいので少しは楽です。 あと、Discord の token は Instance の metadata として持たせて起動時に .env に反映してるので、token 変わっても metadata 書き換えて再起動すれば動くはずです。

と思ってスクリプト書いたものの、結局ググったら無料枠で使える f1-micro という共有コアの低スペックな VM で動かしてる方もいて、まあ確かにそれで立てっぱなしでもいい気がしてきた(ので、上記リポジトリは f1-micro で動くようになってます)。

おしまい。

細かすぎて伝わらない:Azureユーザから見たAzureとGCPの違い

この記事は Google Cloud Platform Advent Calendar 2020 の 22 日目の記事です。

私は昨年まで3年以上、どっぷりと Azure の世界でアプリケーション開発を行っていましたが、転職に伴って主に GCP を使うようになりました。その中で、Azure と GCP では、同じパブリッククラウドなので似ているところもありつつ、最初使うときには少し戸惑うような細かな違いを感じることもありました。

違いと言っても、サービスとして大きく特徴があるところ(例:.NET Framework アプリをマネージドサービスで動かすなら Azure が強い、データ分析なら BigQuery を中心として GCP が強い、等)はすでに山のように情報が存在していてググればすぐ見つかると思うので、本記事では現場で実際に手を動かす人が気になる(かもしれない)めちゃくちゃ細かい部分 にフォーカスしてつらつらと書き残しておきます。

今 Azure をメインで使っていてこれから GCP も挑戦してみたい、という奇特な(?)方にとっては少しは参考になるかもしれません。

基本用語の対応

まず最初に前提知識として、Azure、GCP それぞれで利用する基本的な用語をまとめておきます。 細かい機能性などでもちろん違いはありますが、ざっくりとした概念としては、以下のような対応関係となっています。

概念 Azure GCP
Web の管理画面 Azure Portal Cloud Console
リソースをまとめる単位 リソースグループ プロジェクト
会社などのドメインに紐づく単位 (Azure AD) テナント 組織

ではさっそく1つ目に行きましょう。

1. サービスを利用するには API の有効化が必要

まず GCP を使い始めて最初に私が違和感を感じたのは、GCP では、新しくプロジェクトを作成したあとデフォルト状態では、すべてのサービス(の API)が利用不可(=無効)の状態になっている、という点です。デフォルトですべて無効になっているので、言い換えると、GCP では何かのサービスをはじめて使う際には CLI や Cloud Console の画面から、そのサービスの API を有効化する操作を行う必要があります。

ただし、有効化の作業といってもそんな大層な話ではなく、実作業としては CLI ならコマンドを一つ叩く、あるいは Cloud Console からなら「有効にする」というボタンをポチッと押すぐらいの話なので、一旦慣れれば特に違和感は感じなくなるとは思います。

f:id:nosunosu:20201222165348p:plain
Console から API 有効化を行う画面の例

人によっては少し面倒と思う気もしますが、このように明示的に Opt-in しないと利用できない仕様になっているのは、 不正利用や誤課金を避けるため ということのようです。*1

2. リソース階層と課金管理の考え方の違い

どちらのクラウドでも、リソースを階層化して管理するための仕組みを持っています。 具体的には、Azure では、最上位に Azure AD テナント があり、その下に 管理グループ(なくても良い) -> サブスクリプション -> リソースグループ -> リソース(VM 等) 、という階層になっています。

f:id:nosunosu:20201223062616p:plain
Azure でのリソース階層

一方 GCP では、最上位に 組織 があり、その下に フォルダ(なくても良い) -> プロジェクト -> リソース(VM 等) という階層となっています。見比べていただくと、階層の構造自体はおおむね似たような形式で管理できることがわかると思います。

f:id:nosunosu:20201223092759p:plain
GCP でのリソース階層

ただ一点異なっているのが、利用した費用の請求情報をどうやって紐づけるのか、という点です。 まず Azure では、サブスクリプションが請求先情報を含むような考え方になっています。サブスクリプションの作成時にクレジットカード等の支払い情報を指定し、それが密接不可分なものとして扱われるイメージです。*2

一方で GCP では、請求情報(請求先アカウント)は独立したエンティティとして扱われていて、それを適宜各プロジェクトに紐づけていく仕組みになっています。一度請求先アカウントに紐付けたプロジェクトも、紐付けを解除したり、別の請求先アカウントに変更したりすることが簡単に行えるようになっています。ただし、もちろん紐付けを解除して、請求情報が紐付かなくなってしまったプロジェクトでは、課金処理が行えないため、リソースの作成などは行うことができなくなります。

この違いも、一概にどちらの考え方が良いというものではなく、GCP のように請求情報を分離して管理できることにメリットを感じる人もいれば、一方で GCP ではプロジェクトに必ず請求先アカウントが紐付いていることが保証されないため、それが不便だと感じる人もいるかもしれないなという印象です。

3. Web 管理画面の違い

次に、Azure と GCP の Web 管理画面についてです。これも両者おおむね似てはいるんですが、細かい部分で操作感に違いがあるので、初めての人は戸惑うことがあると思います。

3.1 操作する (リソースグループ|プロジェクト) の指定方法

Web 管理画面から、何らかの操作や設定変更を行うことはよくあると思いますが、その際の操作範囲の指定方法が違います。 具体的には、GCP では、画面のヘッダにあるプルダウンっぽい項目で今何が選択されているか、が超重要になっています。

f:id:nosunosu:20201223110147p:plain
ヘッダにあるプルダウンで操作範囲を選択(GCPStudy というプロジェクトを指定した状態)

このプルダウンをクリックすると、前述した 組織 -> フォルダ -> プロジェクト という階層構造の中から、それらのうちいずれか一つを選択する画面が開くのですが、GCP ではここで最初に操作範囲を指定した上で、何らかの操作や設定変更を行っていくことになります。

Azure では、変更を行う範囲は随時自分でメニューからたどっていくようなイメージなので、最初に必ず明示的に操作範囲を指定する、という GCP の考え方は個人的には結構違和感がありました。

少しわかりづらいので一例として、あるプロジェクト X(Azure の場合リソースグループ X)の権限を A さんに付与したい、というケースを想定してみます。 Azure では、Azure Portal の画面上でこれまでどのような作業を行っていたかはあまり意識することなく、左メニューなどからリソースグループ X を探して開き、IAM のメニューから A さんを探して、ロールを指定して付与、という流れになります。

一方 GCP では、まず最初にヘッダにあるプルダウンから、プロジェクト X を選択する必要があります。

f:id:nosunosu:20201222171909p:plain
プロジェクトの選択画面

プロジェクト X を選択した上で、左メニューから IAM の画面を開くと、プロジェクト X についての IAM 画面が開くことになります。あとは Azure と同じ流れで、「追加」画面から A さんと付与するロールを指定して、権限を付与、という流れになります。

というわけで、GCP の Cloud Console を使う際は、ヘッダのプルダウンをまずチェック、というのは覚えておくと良いと思います。

3.2 リソースの見え方

Azure の場合、各サービスで作成したインスタンスやサービス(App Service Plan, SQL Database...等)は基本的にすべて「リソース」として抽象化されているので、あるリソースグループに含まれるリソースも、Azure Portal 上で簡単に確認することができます。

一方 GCP の場合、各プロジェクト内にどのようなサービスのリソースが存在しているかを一覧化するような画面は現時点では提供されていません。Asset Inventory API という機能で、CLI から一覧化を行うことは可能 なので、必要な場合はそちらを利用することになりますが、この点に関して言えば、Azure の方がお手軽ではあります。

おわりに

以上、「実際使っている人なら当たり前にわかっているけど、意外と最初は戸惑うかもしれない」という観点で、Azure と GCP の細かい違いについて書いてみました。 今回はあまり時間がなくて思いつくままにいくつかだけ挙げてみましたが、実際に複数のクラウドを運用することになると、そういった細かい部分が意外とストレスになる気もするので、時間ができたらもう少しどこかに網羅的にまとめておきたいなと思います。

*1:https://cloud.google.com/apis/docs/getting-started?hl=ja&visit_id=637442209891438666-2967646592&rd=1#enabling_apis

*2:ただしこのあたりは、契約形態によってもそれぞれ異なる。詳細は こちら 参照。

Azure API Management で提供している認証系機能の整理

Azure API Management (以下「APIM」)は、バックエンドの Web API とクライアントアプリの間に挟むことで、もろもろの便利な機能を提供する API プロキシサービスだ。 認証関連の機能も提供しているのだが、ドキュメントを読んでも初見だとちょっと紛らわしいところがあるので、簡単に解説する。

というのも、APIM 上で「認証」と名の付く機能は、以下のようにたくさんあり、適当なキーワードでググると、どれがどの用途なのか一見わかりづらい(と思う)。

# 機能の内容 認証される対象 サポートする形式 (参考)ドキュメント
1 アプリ開発者が、APIM の提供する管理画面にログインするための認証 APIM を使ってアプリ開発する開発者 Azure AD, APIM 独自認証 https://docs.microsoft.com/ja-jp/azure/api-management/api-management-howto-aad
2 アプリ開発者が、APIM の提供する開発者用画面で、バックエンド API にテストリクエストを送信するためにトークンを取得する機能 APIM を使ってアプリ開発する開発者 OAuth2.0, OpenID Connect https://docs.microsoft.com/ja-jp/azure/api-management/api-management-howto-oauth2
3 APIM が、APIM の裏にあるバックエンド API から認証してもらうための機能 APIM の管理者(≒1 の開発者の場合もある) Basic 認証、クライアント証明書 https://docs.microsoft.com/ja-jp/azure/api-management/api-management-authentication-policies
4 アプリ利用者が送ってきたリクエストに付いた JWT Token (*1) を検証する機能 APIM を使って作られたアプリのエンドユーザ OAuth2.0, OpenID Connect 等 https://docs.microsoft.com/ja-jp/azure/api-management/api-management-access-restriction-policies#ValidateJWT

(*1) JWT Token(読みは「じょっと とーくん」)とは、JSON 形式のクレーム情報を base64エンコードしたもので、OpenID Connect 等での認証に利用される。以下の記事が詳しい。

qiita.com

個人的には、API プロキシの提供する認証機能と聞いて期待するのは「クライアントアプリからのリクエストに正しい認証トークンが付いているかチェックしてくれる機能」だと思うが、それは上記の表だと 4 にあたる。

なお、最初よくわからなかったのが 2 なのだが、結局この機能は、APIM の開発者向け画面からテストリクエストを送ってみる際にしか使えない開発用機能なので要注意。

validate-jwt ポリシーの使い方

さて、1~3については、表中に示したドキュメントを見てもらうとして、肝心の 4 の使い方について簡単に説明しておく。

4 の機能は、validate-jwt というポリシーとして提供されている。 ポリシーというのは、API Management の API で受けたリクエストやレスポンスを、パイプラインのXMLな感じで加工したり、追加処理をしたりできる機能のことで、適用したいポリシーを XML で記述して利用する。

では validate-jwt ポリシーがどのような処理を行ってくれるかというと、リクエストのヘッダ(通常であれば Authorization ヘッダ)に含まれる JWT Token を復号化し、任意の条件を満たしているかチェックして、リクエストを通過させたり、拒否したりできるものだ。

Azure ポータルで APIM を開き、以下の画面でポリシーを編集する。

f:id:nosunosu:20180705125830p:plain

以下のポリシーを <inbound /> に追加すると、Azure AD の特定のテナントとアプリで認証されたユーザをすべて許可するルールとなる。

<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">  
    <!-- 利用する認証基盤の OpenID 構成エンドポイントを指定 -->
    <!-- e.g.) Facebook の場合は https://facebook.com/.well-known/openid-configuration -->
    <openid-config url="https://login.microsoftonline.com/contoso.onmicrosoft.com/.well-known/openid-configuration" /> 

    <!-- 認証時に指定した Resource 名 --> 
    <audiences>
        <audience>https://graph.microsoft.com</audience>
    </audiences>
</validate-jwt>  

Azure AD で特定のグループに所属していることをチェックして、簡易的な認可を行うこともできる。

<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">  
    <openid-config url="https://login.microsoftonline.com/contoso.onmicrosoft.com/.well-known/openid-configuration" /> 
    <audiences>
        <audience>https://graph.microsoft.com</audience>
    </audiences>

    <required-claims>
        <claim name="groups" match="any">
            <!-- 許可するグループの ID を指定 -->
            <value>xxxx-xxxx-xxxx-xxxxxxxxx</value>
        </claim>
    </required-claims>
</validate-jwt>  

なお、デフォルトでは JWT Token にグループの情報は含まれないため、事前に Azure AD のアプリ登録画面のマニフェストで、"groupMembershipClaims": "SecurityGroup" などに変更しておく必要がある。

stackoverflow.com

以下のドキュメントにも validate-jwt を使ったポリシーのサンプルが記載されているので、利用する際には目を通してみると良い。

docs.microsoft.com

docs.microsoft.com

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

Blazor でかんたんなサンプル SPA を作った

前回の記事では、Blazor についてざっくり解説したが、せっかくなのでシンプルなサンプルも作ってみた。

github.com

動くデモはこちらGitHub からリポジトリを検索して表示するだけのかんたんなサンプル。

Containers/Components で分けて、状態の管理とか API への問い合わせとかは Containers 側に集約するような構成にしてみた。 ブラウザから GitHub API への問い合わせは、Container に @inject HttpClient httpClient と書けば、HttpClientインスタンスが Inject されてくるので、普通に GetJsonAsync<T>() するだけでちゃんと動く。

@inject HttpClient httpClient

@functions {
    var result = await httpClient.GetJsonAsync<T>(uri);
}

いろいろ雑だけど、とりあえず動くものはパパっと作れますよという例として。