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);
}

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

.NET で SPA が書ける Blazor の使い方をざっくり解説

Blazor というのは、SPA のクライアントアプリを .NET で書くと、WebAssembly 上で .NET のバイナリのまま動かすことができるというフレームワークだ。

一見かなりアクロバティックな仕組みだし、.NET 界隈だと Silverlight を思い出して「うっ頭が…」となる人もいるようだが、まあ今回は独自プラグインではなく、Web 標準 である WebAssembly にちゃんと乗ってるところが大きな違い。

完成度としては、「実験的なプロジェクトなので Production では使わないこと」とうたってはいるが、実際使ってみるとすでに基本的な機能はかなりできあがっていてそこそこちゃんと動くので、使い方をざっくりまとめておく。

※ちなみに Blazor という名前の由来は、公式 FAQ によると "Browser + Razor = Blazor" とのこと。なぜ二文字目がRじゃなくてLなのかは謎。

開発環境の構築

.NET Core 2.1 RC SDK のインストール

まずは、.NET Core 2.1 SDK の最新版をインストールする。2018年5月17日時点で最新バージョンは2.1.300-RC1。

www.microsoft.com

Visual Studio 2017 Preview のインストール

.NET Core 2.1 を使うには、Preview 版の Visual Studio が必要となる。以下のリンクからインストールする。

www.visualstudio.com

なお Preview 版は、通常版の Visual Studio 2017 と同居させられる作りになっている。通常版を利用している人も何も考えずにインストールしてしまって大丈夫。 同居させると、インストーラの画面はこんな感じになる。

f:id:nosunosu:20180518120526p:plain

テンプレートからプロジェクトを新規作成

プロジェクトの新規作成で、「ASP.NET Core Web アプリケーション」から、「Blazor (ASP.NET Core hosted)」を選択する。

今回は「Blazor (ASP.NET Core hosted)」を選択して、サーバサイドの Web API を含んだソリューションを作成するが、すでに Web API が別にあり、純粋にクライアントサイドのみ開発したい場合には、クライアントアプリのみのプロジェクトが生成される「Blazor」を選択する。

f:id:nosunosu:20180518120553p:plain

※「Blazor」を選択した場合、ビルドして生成されるのは単なる静的ファイル群なので、静的な Web サイトをホストできるサービスであればどこにでも配置して公開できる。一方「Blazor (ASP.NET Core hosted)」の場合は、当然ながら ASP.NET Core が動く環境でである必要がある。

なおいずれの場合も、ランタイムは ASP.NET Core 2.1 または 2.0 を選択すること。

プロジェクトを作成すると、以下のようなプロジェクト構成のソリューションが作成される。

f:id:nosunosu:20180518120649p:plain

  • [プロジェクト名].Client
  • [プロジェクト名].Server
  • [プロジェクト名].Shared

ほぼ自明ではあるが、それぞれ以下のような役割のプロジェクトとなる。

.Client

Blazor を使ったクライアントアプリの本体部分。 見た目上は、.cshtml ファイルが Pages フォルダ内に配置されていて、Razor Pages のプロジェクトっぽい(Blazor が Razor の拡張なので当たり前といえば当たり前)。

Blazor 用に以下の NuGet パッケージがインストールされている。 - Microsoft.AspNetCore.Blazor.Browser - Microsoft.AspNetCore.Blazor.Build

.Server

サーバサイドの Web API。 中身は普通の ASP.NET Core Web API なので、特に難しいところはない。

実行時には、.Client は、この .Server プロジェクトの ASP.NET Core 上にホスティングされる状態になる。 具体的には、.Server プロジェクトから .Client プロジェクトを参照し、さらに以下のように .Server プロジェクトの Startup クラス Configure() 内で app.UseBlazor<T>() を呼ぶことで、ホスティングする Blazor クライアントプロジェクトを指定している。

Startup.cs

public class Startup {
    public void ConfigureServices(IServiceCollection services) { ... }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
        ...
        app.UseBlazor<Client.Program>();
    }
}

.Shared

クライアントとサーバ両方から参照されるクラスライブラリ。 主にはクライアントとサーバでやりとりする JSONシリアライズするためのクラスを配置するのに使用する。

とりあえず実行してみる

スタートアッププロジェクトは .Server を選択して、デバッグ実行する。 初期状態では、クライアント単独で動くカウンタと、Web API から取得した天気情報を表示するサンプルページが用意されている。

なお、デバッガはまだ開発中なので、Blazor のコンポーネント内にブレイクポイントを置いても動作しない。

ここまででセットアップは終わりなので、あとはアプリを書いていくだけだ。 以降では、Blazor アプリの書き方の基本を少し説明する。

Blazor アプリの書き方

公式の FAQ に、"inspired by existing modern single-page app frameworks, such as React, Angular, and Vue" とあるだけあって、React などの JavaScript の UI フレームワークの考え方がいろいろごっちゃになって取り入れられている。

基本的な使い方としては、C#/HTML からなるコンポーネントという塊を作り、それを入れ子にして組み合わせることでアプリケーションを構成する、というおなじみの仕組みになっている。

コンポーネントを作成するには、以下の3つのやり方がある。

  • インライン (.cshtml) 方式
  • コードビハインド方式
  • クラス方式

まずはとりあえず、一番わかりやすいインライン方式で説明する。

インライン方式でのコンポーネント作成

インライン方式では、一つの .cshtml ファイルが一つのコンポーネントとなる。 .cshtml ファイルなだけあって、従来の Razor 構文で View を記述すれば良いのだが、大きな違いとして、Blazor コンポーネントでは「コンポーネント変数」を持つことができる。

コンポーネント変数は、.cshtml の @functions{} 内で[Parameter] 属性を付けた変数として宣言する。 これにより、React における Props のように、コンポーネントを使う親コンポーネント側で属性値として値を渡すことができる。

また、コンポーネント名のタグに挟まれたテキストの値は、RenderFragment 型の ChildContent という名前のコンポーネント変数(名前は変更不可)を定義しておくことで、受け取ることができる。

Child.cshtml

<!-- 子コンポーネント -->
<h1>@Title</h1>
<p>@ChildContent</p>

@functions {
    [Parameter]
    private string Title { get; set; }

    [Parameter]
    private RenderFragment ChildContent { get; set; }
}

Parent.cshtml

<!-- 親コンポーネント -->
<Child Title="ここでタイトルを渡す">ここでChildContentを渡す</Child>

親から子には、Action を渡すこともできる。 これを使うと、React 風に、Presentational/Container でコンポーネントを分け、子コンポーネント(Presentational)で発生したイベントを親コンポーネント(Container)に伝えて、ロジックは親側に集約するようなことができる。 子コンポーネントでは、渡された Action を任意の契機で Invoke() すれば良い。

ただし、イベント契機で親が管理する値を変更した場合、手動で StateHasChanged() を叩かないと、画面上に変更は反映されないことに注意する。 非公式ドキュメントには、StateHasChanged() は親と子で独立に動くので、親と子両方に反映するには双方で呼ぶ必要があるという記載があるが、試した限りでは、親側でさえ呼べば、子側にも変更が反映された。

以下のサンプルでは、子側が持つ「Count!」ボタンを押下すると、親側の Action が実行されて currentCountOnParent がカウントアップされ、その変更が子側にも伝播することで、両方の表示が更新される。

コンポーネント

ParentComponent.cshtml

@page "/parentComponent"

<h1>親子での連携テスト</h1>
<p>Count on Parent: @currentCountOnParent</p>
<ChildComponent currentCountOnChild=@currentCountOnParent OnChildButtonClicked=@ChildButtonClicked />

@functions {
    private int currentCountOnParent = 0;

    private void ChildButtonClicked() {
        Console.WriteLine("ChildButton is clicked");
        currentCountOnParent++;

        // 手動で描画を更新する
        StateHasChanged();
    }
}

コンポーネント

ChildComponent.cshtml

<p>Count on Child: @currentCountOnChild</p>
<button onclick=@OnClick>Count!</button>

@functions {
    [Parameter] private int currentCountOnChild { get; set; }   
    [Parameter] private Action OnChildButtonClicked { get; set; }

    private void OnClick() {
        OnChildButtonClicked?.Invoke();
    }
}

インライン方式以外のコンポーネント作成方法

インライン方式以外での記述方法は以下のとおり。

コードビハインド方式

C# のロジック部分と HTML(Razor) を分けて記述する方式。 具体的には、C# 部分は BlazorComponent を継承するクラスに記述し、View 部分のみ .cshtml に記述した上で、@inherits 文を使ってロジックのクラスを指定する。

ChildBase.cs

public class ChildBase : BlazorComponent {
    [Parameter]
    private string Title { get; set; }

    [Parameter]
    private RenderFragment ChildContent { get; set; }
}

ChildByCodeBehind.cshtml

@inherits ChildBase

<h1>@Title</h1>
<p>@ChildContent</p>

クラス方式

純粋に C# のクラスとしてコンポーネントを作成することもできる。 実は、他の 2 方式で作ったコンポーネントも、プロジェクトをビルドすると [コンポーネント名].g.cs というファイル名でこの形式のクラスに自動的に変換されている。

クラス内の BuildRenderTree() で、HTML の要素を一つずつ組み立てていく必要があるため、必要がない限りあえて用いる必要はない。

ChildByClass.cs

public class ChildComponent : BlazorComponent {
    private string Title { get; set; }
    private RenderFragment ChildContent { get; set; }

    protected override void BuildRenderTree(RenderTreeBuilder builder) {
        builder.OpenElement(1, "h1");
        builder.AddContent(2, Title);
        builder.CloseElement();
        builder.OpenElement(3, "p");
        builder.AddContent(4, ChildContent);
        builder.CloseElement();
    }
}

ルーティング

作成したコンポーネントにアクセスするためには、ルーティングを定義してやる必要がある。

インライン方式・コードビハインド方式の場合は、.cshtml の先頭に @page "/path/to/component" という形式で宣言してあげるだけで良い。

Parent.cshtml

@page "/parent"
@page "/parent2" // 一つのコンポーネントに複数のパスを定義することもできる
@page "/parentWithParam/{parentId}" // URLからパラメータを受け取ることもできる

<!-- 親コンポーネント -->
<Child Title="ここでタイトルを渡す">ここでChildContentを渡す</Child>

@functions {
    [Parameter] private int parentId { get; set; }
}

クラス方式の場合は、[RouteAttribute()] を使用する。

[RouteAttribute("/parent")]
[RouteAttribute("/parent2")]
[RouteAttribute("/parent3/{parentId}")] // これは未確認…
public class ParentComponent : BlazorComponent { ... }

ライフサイクルメソッド

これも React などと似た感じで、コンポーネントのライフサイクルメソッドが用意されている。 実際には違う点も多いが、イメージをつかむための参考までに類似の React メソッドも記載している。

メソッド名 (参考:類似の React メソッド) 用途
OnInit() componentWillMount() コンポーネントの初期化直後に一度だけ呼び出される
OnParametersSet() componentWillReceiveProps() コンポーネント変数が割り当て(変更も含む)されるごとに呼び出される
OnAfterRender() componentDidUpdate() コンポーネントの初期描画が完了、または更新されるごとに呼び出される
ShouldRender() ShouldRender() パフォーマンスチューニングなどで描画を更新したくない場合、ShouldRender()false を返すようにする

使う際には、対象のコンポーネント内で以下のようにメソッドを override すれば良い。

@functions {
    protected override void OnInit() {
        Console.WriteLine("OnInit() called");
    }

    protected override void OnParametersSet() {
        Console.WriteLine("OnParametersSet() called");
    }

    protected override void OnAfterRender() {
        Console.WriteLine("OnAfterRender() called");
    }
}

おまけ:Blazor で Flux するには

たぶん Blazor で Flux しようとする人はいるんだろうなと思ってググったところ、blazor-redux というプロジェクトがすでに存在していた。

github.com

Redux 風の APIC# で実装していて、Redux を知っていればすぐに使えそう。 ちゃんとブラウザ上で Redux DevTools を使って State を見られるようにしてあるようでえらい。

で、こういうのが必要かどうかっていうのは結局 JS のフロント開発と一緒で、開発規模がかなり大きくなれば、必要になってくるんだろうとは思う。

参考

learn-blazor.com

codedaze.io

Microsoft Graph(Microsoftクラウドサービスの統合API)の使い方

Microsoft Graph を使うと、OneDrive (for Business) や SharePoint, Outlook, Teams 等を含む多くの Microsoftクラウド製品を、標準化された REST API 経由で操作できる。 developer.microsoft.com

何ができるかはの詳細は上記リンク先に記載されているが、以下のようなリソースの主要な操作はだいたい行うことができる。

今回は例として、O365を契約している前提で、OneDrive for Business からファイルを取得する場合の手順を説明する。 利用する際は、REST API を直接叩くこともできるが、SDK が提供されている言語の場合は、SDK を使うのが楽で良い。提供されている SDK は以下のとおり。今回は、.NET SDK を利用する。

目次

1. アプリの登録

事前に、Graph にアクセスするアプリケーションを認証基盤側に登録する必要がある。

1.1 アプリの作成

Azure AD の認証エンドポイントには、v1.0 と v2.0 の 2 つがあり、それぞれアプリ登録を行う場所が異なる(v1.0 と v2.0 の違いについては後述)。今回は v1.0 を利用するので、Azure Portal の Azure AD 管理画面からアプリ作成を行う。

詳細な手順については、以下を参考にすると良い。 docs.microsoft.com

「アプリケーションの種類」は「Web アプリ/API」を選択する。また、作成したアプリの画面に表示される アプリケーションID が、後ほど使う Client ID となるので、メモしておく。

f:id:nosunosu:20180626121314p:plain

1.2 Client Secret の生成

アプリ作成後には、メニューからキーを生成しておく。これが後ほど用いるClient Secretとなるので、同じくメモしておく。

f:id:nosunosu:20180626121325p:plain

1.3 アプリへの権限付与

さらに、作成したアプリに対して、Graph で利用したいリソースへのアクセス許可を付与する。 アプリの「設定」から「必要なアクセス許可」→「追加」と進み、API として「Microsoft Graph」を、アクセス許可としては「アプリケーションのアクセス許可」から「Read all groups」を選択して保存する。

f:id:nosunosu:20180626121338p:plain

そのうえで、「必要なアクセス許可」の画面から再度「Microsoft Graph」を選択し、「アクセス許可の付与」→「はい」を選択する。

なお、あとから権限を削除したい場合も、削除したいアクセス許可のチェックを外して保存したあとに、同様に「アクセス許可の付与」を再度行う必要があるので注意する。

行いたい処理に対して、どのようなアクセス許可が必要かは、以下のドキュメントで確認できる。

アクセス許可 - ドキュメント - Microsoft Graph

2. アプリ実装

2.1 認証の流れ

アプリの登録が終わったら、Visual Studio でコンソールアプリを作成し、以下の NuGet パッケージをインストールする。

  • Microsoft.IdentityModel.Clients.ActiveDirectory
  • Microsoft.Graph

事前準備で作成したアプリの Client ID, Client Secret を使って認証トークンを Azure AD から取得し、そのトークンを使って認証する Graph 用クライアントのインスタンスを作成する。

// TODO: "hogehoge"部分を、アクセス先となる Azure AD テナントの名前に置き換える
var authority = "https://login.microsoftonline.com/hogehoge.onmicrosoft.com";
var authContext = new AuthenticationContext(authority, null);

// TODO: Azure Portal で作成したアプリの Client ID, Client Secret に置き換える
var clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var clientCredential = new ClientCredential(clientId, clientSecret);

// 指定したリソースへのアクセス権限を持った認証トークンを取得する
var graphResourceUri = "https://graph.microsoft.com";
var authResult = await authContext.AcquireTokenAsync(graphResourceUri, clientCredential);
var accessToken = authResult.AccessToken;

// リクエスト送信時に認証トークンを利用するように設定した、GraphServiceClient のインスタンスを作成する
var graphServiceClient = new GraphServiceClient(
    new DelegateAuthenticationProvider((requestMessage) => {
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
        return Task.FromResult(0);
    })
);

あとは、作成した GraphServiceClientインスタンスを使って、Microsoft Graph へのリクエストを送信する。

// Group のリストを取得
var groups = await graphClient.Groups.Request().GetAsync();

// 各 Group のデフォルトの Drive と、Drive のルートにあるファイル・フォルダのリストを取得する
foreach(var group in groups) {
    Console.WriteLine($"Group Name: {group.DisplayName}, Group ID: {group.Id}");

    var drive = await graphClient.Groups[group.Id].Drive.Request().GetAsync();
    Console.WriteLine($"Drive Name: {drive.Name}, Drive ID: {drive.Id}");

    var rootItems = await graphClient.Drives[drive.Id].Root.Children.Request().GetAsync();
    Console.WriteLine($"Items in the root folder:");
    foreach(var item in rootItems) {
        Console.WriteLine($"  - {item.Name}");
    }
}

2.2 例外ハンドリング

GraphServiceClientServiceException 型の例外を投げる。ServiceException の持つ IsMatch() を使うと、エラーの種別を判別することができる。

try { ... }
catch(ServiceException ex) {
    if(ex.IsMatch(GraphErrorCode.ItemNotFound.ToString())) {
        Console.WriteLine("指定したファイルが見つかりませんでした。");
    }
}

この場合、ItemNotFound のエラーなので、その旨のエラーメッセージを出力している。

その他 GraphErrorCode が持つエラーの種別については、以下に一覧が記載されている。

https://developer.microsoft.com/en-us/graph/docs/concepts/errors

3. 補足

3.1 Web アプリとネイティブアプリでの認証の違い

少し話は脱線するが、Web アプリや Web API から Graph にアクセスする場合と、デスクトップ・スマホなどネイティブアプリから直接 Graph にアクセスする場合で、用いるべき認証の方式が異なる。

前者の場合は、Client IDClient Secretの組み合わせで認証される"Service Principal"と呼ばれるアプリを示すアカウントの権限を用いて、Graph にアクセスすることができる。一方後者の場合は、Client Secret を安全に秘匿することができないため、実際にアプリを使うユーザが自身のアカウントを使って Azure AD の画面からログインすることで、そのユーザ自身の権限を用いて Graph にアクセスする。

今回のサンプルでは、説明の都合上コンソールアプリで Service Principal を利用しているが、実利用においては正しく使いわけること!

3.2 Azure AD v1.0 と v2.0 の違い

Azure AD v1.0 と v2.0 の違いは以下に詳しく説明されている。 docs.microsoft.com

大きな違いとしては、以下のような点がある。

  • v1.0 は Azure AD アカウントのみをサポートしていたのに対して、v2.0 ではさらに Microsoft Account もサポートしている
  • 認証に使うライブラリも、v1.0 は従来の ADAL を利用するのに対して、v2.0 では MSAL と呼ばれる、新しい認証ライブラリを使う

UnityやAutofacでDIする際に、同じ型の複数のインスタンスを注入する方法

.NETで開発をしていると、クラスを別の依存クラスの実装と分離するために、Dependency Injection(DI)を使うことは多い。 その際に、一種類の依存するインタフェースにつき、一つだけインスタンスを渡せば良いのであれば話は単純なのだが、一つのインタフェースでも、コンストラクタの異なる複数のインスタンスを渡したい、というようなケースがある。

例えば、以下のようなケースである。

  • IHttpClient (実装は HttpClient) という、何らかのHTTPリクエストを送るためのクライアントがある
  • HttpClient はコンストラクタの引数としてHTTP接続の際のベースURLを受け取るとする。
  • このクライアントを利用する SampleController では、2つの異なるベースURLに接続する必要があるため、それぞれコンストラクタの異なる2つのHttpClientのインスタンスを利用したい

以下のやり方で、このような場合にも対応できる。

Unity を使っている場合

DIのライブラリにUnityを使っている場合、Unityの持つ名前付きでRegisterTypeする機能を使う。

var BaseUriA = "a.example.com"; // 1つ目のベースUri
var BaseUriB = "b.example.com"; // 2つ目のベースUri

var container = new UnityContainer();
container.RegisterType<IHttpClient, HttpClient>(
    "HttpClientA", // 一つ目の引数として任意の名前を渡す
    new InjectionConstructor(BaseUriA)
);
container.RegisterType<IHttpClient, HttpClient>(
    "HttpClientB",
    new InjectionConstructor(BaseUriB)
);

// 上でつけた名前を使って、2つのHttpClientをSampleControllerのコンストラクタに渡す
container.RegisterType<SampleController>(new InjectionFactory(c =>
    new SampleController(
        c.Resolve<IHttpClient>("HttpClientA"),
        c.Resolve<IHttpClient>("HttpClientB")
    )
));
public class SampleController : ApiController {
    private IHttpClient HttpClientA;
    private IHttpClient HttpClientB;

    public SampleController(IHttpClient HttpClientA, IHttpClient HttpClientB) {
        this.HttpClientA = HttpClientA;
        this.HttpClientB = HttpClientB;
    }

}

Autofac を使っている場合

Unityはもはやメンテされていないため、今後新しいプロジェクトでは利用しないことが望ましい。代替としてよく名前の上がるAutofacの場合、Named and Keyed Services という機能で目的の挙動が実現できる。 依存を受け取る側で、IIndex<K, V>という型で複数インスタンスを受け取り、名前を指定することで目的のインスタンスを取り出す。

var BaseUriA = "a.example.com"; # 1つ目のベースUri
var BaseUriB = "b.example.com"; # 2つ目のベースUri

var builder = new ContainerBuilder();
builder.RegisterType<HttpClient>()
    .WithParameter(new TypedParameter(typeof(string), BaseUriA))
    .Named<IHttpClient>("HttpClientA");
builder.RegisterType<HttpClient>()
    .WithParameter(new TypedParameter(typeof(string), BaseUriB))
    .Named<IHttpClient>("HttpClientB");
public class SampleController : ApiController {
    private IIndex<HttpClient, IHttpClient> httpClients;

    public SampleController(IIndex<HttpClient, IHttpClient> httpClients) {
        this.httpClients = httpClients;
    }

    public IHttpClient GetClientA() {
        return httpClients["HttpClientA"];
    }
}

参考

地味に便利な mkpasswd でランダムパスワードを生成する

いろいろな条件でパスワード生成できる mkpasswd コマンドが地味に便利。 CentOS の場合デフォルトでは入っていないので、下記にてインストールする。

$ sudo yum install expect

オプションにて、文字数や記号・数字の数を指定できる。

オプション 意味
-l 数字 パスワードの長さを指定(デフォルトは9)
-d 数字 パスワードに含める数字の数を指定(デフォルトは2)
-c 数字 パスワードに含めるアルファベット(小文字)の数を指定(デフォルトは2)
-C 数字 パスワードに含めるアルファベット(大文字)の数を指定(デフォルトは2)
-s 数字 パスワードに含める特殊文字(記号など)の数を指定(デフォルトは1)
-2 パスワードが、右手と左手で交互に入力される文字配列となる(QWERTYキーボードの場合)

個人的によく使うのは、下記の「8文字・数字2桁・記号なし」。

$ mkpasswd -l 8 -d 2 -s 0

参考

任意の文字数でパスワードをランダム生成するには - @IT