yamamoWorks

.NET技術を中心に気まぐれに更新していきます

MSALをXamarin.Forms+Prismで使う

本エントリーは Xamarin Advent Calendar 2016 (その2) 22日目のエントリーです。

今回はMicrosoftの認証ライブラリ「Microsoft Authentication Library」のXamarin.Forms + Prismで構成されたモバイルアプリでの実装方法を紹介します。

Microsoft Authentication Library (MSAL)

以前からAzure ADをモバイルアプリケーションの認証で利用する為に Active Directory Authentication Library (ADAL) というライブラリが提供されています。
これに対し Microsoft Authentication Library (MSAL) はADALの後継とされるライブラリで、Azure ADの他にMicrosoftアカウントやAzure AD B2Cの認証も1つのプログラミングモデルでカバーするライブラリとしてBuild 2016で発表されました。もちろんXamarinからも使用可能なライブラリです?

Preparation

Azure側の登録作業は公式ブログの記事にある通りなので割愛します。

Authenticate Your Mobile Apps Using Microsoft Authentication Library

Design

Xamarinの公式ブログでもMSALの実装方法は紹介されているのですが、各プラットフォーム毎にPageRendererでログインページを実装しコードビハインドで実現する方法になっているので、ここではPrismのPlatformInitializerを用いた方法で実装したいと思います。
PlatformInitializerについてはnuitsさんのブログを参考にして頂くのが良いかと思います。 www.nuits.jp

MSALの使い方の概要は以下の通り。

  1. PublicClientApplicationのインスタンスを生成する
  2. PublicClientApplicationのインスタンスにPlatformParametersを設定する
  3. PublicClientApplication.AcquireTokenAsync()メソッドで認証を実行する

非常に簡単なのですが、重要なのが2つ目のPlatformParametersを設定する部分です。 これは名前の通り各プラットフォーム固有のパラメータを扱うもので、実態はMSALライブラリ側でWebページの認証画面を表示する為にiOSのUIViewControllerやAndroidのActivityを渡す仕組みになっています。

Xamarinの公式ブログの実装方法ではAppクラスにPublicClientApplicationのプロパティを用意し、カスタムレンダラーで作成した各プラットフォームのログインページからPlatformParametersを渡す方法となっていますが、今回はPCLプロジェクトにIPlatformParametersFactoryインターフェイスを定義し、各プラットフォームでPlatformParametersを返す処理を実装します。

Implementation

IPlatformParametersFactory

まずPCLプロジェクトにIPlatformParametersFactoryインターフェイスを以下のように定義します。

namespace MSALApp
{
    public interface IPlatformParametersFactory
    {
        IPlatformParameters GetParameters();
    }
}

次に各プラットフォームでIPlatformParametersFactoryを実装します。

Android

namespace MSALApp.Droid
{
    class PlatformParametersFactory : IPlatformParametersFactory
    {
        public IPlatformParameters GetParameters()
        {
            // Xamarinの公式ブログではPageRender.Context as Activityを渡している
            return new PlatformParameters(Forms.Context as Activity);
        }
    }
}

iOS

namespace MSALApp.iOS
{
    class PlatformParametersFactory : IPlatformParametersFactory
    {
        public IPlatformParameters GetParameters()
        {
            // Xamarinの公式ブログではPageRender自身を渡している
            return new PlatformParameters(UIApplication.SharedApplication.KeyWindow.RootViewController);
        }
    }
}

UWP

namespace MSALApp.UWP
{
    class PlatformParametersFactory : IPlatformParametersFactory
    {
        public IPlatformParameters GetParameters()
        {
            return new PlatformParameters();
        }
    }
}

IPlatformInitializer

次にPrism (Unity) に各プラットフォームのPlatformParametersFactoryを登録します。 Prismのプロジェクトテンプレートからプロジェクトを作成していればIPlatformInitializerを実装するクラスが各プラットフォームに自動生成されています。

  • Android : MainActivity.cs -> AndroidInitializer
  • iOS : AppDelegate.cs -> iOSInitializer
  • UWP : MainPage.xaml.cs -> UwpInitializer

RegisterTypesに記述するコードはどのプラットフォームも同じです。

public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IUnityContainer container)
    {
        container.RegisterType(typeof(IPlatformParametersFactory), typeof(PlatformParametersFactory));
    }
}

View

LoginCommandをバインドしたログインボタンとユーザ情報を表示するラベルを配置しておきます。

<StackLayout Orientation="Vertical" Margin="20,40">
    <Button Text="Login" Command="{Binding LoginCommand}" />
    <Label Text="{Binding User.Name}" />
    <Label Text="{Binding User.DisplayableId}" />
</StackLayout>

ViewModel

ViewModelのコンストラクタ引数にIPlatformParametersFactoryインターフェイスのパラメータを定義し、各プラットフォームで実装したPlatformParametersFactoryを受け取れるようにします。

ここで注意しなければならないのはPublicClientApplicationにPlatformParametersをセットするタイミングです。ViewModelのコンストラクタでPublicClientApplicationのインスタンスを生成した時点でセットしたいところですが、このタイミングではまだUIApplication.SharedApplication.KeyWindow.RootViewControllerがnullなので、ログインボタンを押した処理の中でPlatformParametersFactoryから取得します。

namespace MSALApp.ViewModels
{
    public class MainPageViewModel : BindableBase
    {
        public DelegateCommand LoginCommand { get; }

        private User _user;
        public User User
        {
            get { return _user; }
            set { SetProperty(ref _user, value); }
        }

        public MainPageViewModel(IPlatformParametersFactory parametersFactory)
        {
            var client = new PublicClientApplication("your-app-id");

            LoginCommand = new DelegateCommand(
                async () =>
                {
                    if (client.PlatformParameters == null)
                    {
                        client.PlatformParameters = parametersFactory.GetParameters();
                    }

                    var result = await client.AcquireTokenAsync(new[] { "User.Read" });

                    User = result.User;
                });
        }
    }
}

Finish

これでMVVMなスッキリした実装となりました。

Android iOS UWP
f:id:yamamoWorks:20161218190544g:plain f:id:yamamoWorks:20161218190550g:plain f:id:yamamoWorks:20161218190554g:plain