読者です 読者をやめる 読者になる 読者になる

yamamoWorks

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

MSALをXamarin.Forms+Prismで使う

Xamarin Azure

本エントリーは 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