3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Xamarin.Forms + Azure Mobile Apps 認証処理 Getting Started (2/2)

Posted at

概要

この記事の記載内容

  • Xamarin.Forms + Azure Mobile Appsで認証処理を行う
  • 公式ドキュメントだと代替案が大量にあり大混乱したので、余計な代替案は出さず、最低限の説明のみ記載し、認証処理を実現できることを目指す
  • 対象OSはAndroid、iOS
  • 1章は主にAzureの設定
  • 2章はAzure Web Apps、Xamarinのプログラム修正方法(ここ)。公式のReferenceだけではなく、実現したい機能用の処理も追加する

実現したい処理内容

  1. 複数プロバイダ(Office365, Google)でXamarin.Forms + Azure Mobile Appsの認証処理を行う
  2. アプリ起動時、過去にサインインを行っていれば、サインイン画面を再度表示せずに認証後の画面に遷移する
  3. アクセストークンは定期的に更新する

その他・要調査項目

  • Azure Mobile Appsにはサーバー側認証する。クライアント側認証との違いは難しいので良くわかってない
  • GoogleのRefreshトークンの取得が出来ない
  • サインアウト時、アプリからキャッシュ消すだけじゃなくて、ブラウザからもサインアウトしたいけど、やり方わからない

今回解説用に作成するアプリの名称

  • アプリ名:XamarinAuthFirst
  • Azure Mobile Apps名:xamarin-auth-1st
  • Azure Mobile Apps URL: https://xamarin-auth-1st.azurewebsites.net
  • リダイレクト用URLスキーマ: xamarin-auth-1st://easyauth.callback

前回

Azure Mobile Appsプログラム修正

前回ダウンロードした、Azure Mobile AppsのC#ソリューション本体を修正します。
はじめてソリューションを開いた場合、NuGetの復元が必要です。
image.png

認証Attribute追加

認証を行う必要がある、コントローラーもしくはメソッドに[Authorize]属性を追加します。
今回の場合、すべてのコントローラーに追加して問題ないです。
クイックスタートのアプリの場合、TodoItemController、ValuesControllerの両方に、[Authorize]属性を追加します。
image.png

発行

プロジェクトの発行を実施します。
image.png

AzureのMobile Serviceに、プロジェクトの発行を行ってください。
image.png

Xamarin.Formsプログラム修正

前回ダウンロードした、Xamarin.Formのクイックスタートプロジェクトを開きます。
Mobile Apps側と同様に、ソリューションのNuget復元を行って下さい。

PCL修正

共通部分、プロジェクト「xamarin_auth_1st」の修正を行っていきます。
まず、Account処理に関するHelper、AccountHelper.csを作成します。

AccountHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.WindowsAzure.MobileServices;
using xamarin_auth_1st;
using System.Net.Http;
using System.Threading.Tasks;

// NuGetで追加
using Xamarin.Auth;
namespace Xamarin.Auth
{
    public class AccountHelper
    {
        private const string AppName = "your_app_name";

        /// <summary>
        /// 保持しているMobileServiceUser情報を取得
        /// </summary>
        /// <returns></returns>
        public static MobileServiceUser Get()
        {
            // AccountStoreから取得
            var account = AccountStore.Create().FindAccountsForService(AppName).FirstOrDefault();
            // 存在すれば返却
            if (account != null)
            {
                return JsonConvert.DeserializeObject<MobileServiceUser>(account.Properties["MobileServiceUser"]);
            }
            // なければnull返却
            return null;
        }

        /// <summary>
        /// MobileServiceUser情報をAccountStoreにセット
        /// </summary>
        /// <param name="user"></param>
        public static void Set(MobileServiceUser user)
        {
            // Account作成
            Account account = new Account
            {
                Username = user.UserId
            };
            // MobileServiceUserをjson形式に変換して追加
            account.Properties.Add("MobileServiceUser", JsonConvert.SerializeObject(user));

            // すでにあるAccountを削除
            Clear();

            // 保存
            AccountStore.Create().Save(account, AppName);
        }

        /// <summary>
        /// AccountStoreのクリア
        /// </summary>
        public static void Clear()
        {
            var accounts = AccountStore.Create().FindAccountsForService(AppName);
            if (accounts != null)
            {
                foreach (var account in accounts)
                {
                    AccountStore.Create().Delete(account, AppName);
                }
            }
        }

        /// <summary>
        /// トークンのリフレッシュ。MobileServiceのアクセストークンは2時間で切れるので、接続前に最新化する
        /// </summary>
        public static async void Refresh()
        {
            using (var client = new HttpClient())
            {
                try
                {
                    // CurrentUser取得
                    var user = TodoItemManager.DefaultManager.CurrentClient.CurrentUser;
                    if (user == null)
                    {
                        return;
                    }
                    // ヘッダー情報などセット
                    client.DefaultRequestHeaders.Add("X-ZUMO-AUTH", user.MobileServiceAuthenticationToken);
                    // トークン最新化
                    var response = await client.GetAsync(Constants.ApplicationURL + "/.auth/refresh");
                    if (response.IsSuccessStatusCode)
                    {
                        // 取得したjsonを変換し、CurrentUserにセット
                        JToken jObject = JToken.Parse(await response.Content.ReadAsStringAsync());
                        user.MobileServiceAuthenticationToken = Convert.ToString(jObject["authenticationToken"]);
                        Set(user);
                    }
                }
                catch (Exception)
                {
                }
            }
        }

        /// <summary>
        /// アプリ起動時に実施。AccountStoreにユーザー情報あれば、CurrentUserにセット
        /// </summary>
        /// <returns></returns>
        public static void OnInitialized()
        {
            // MobileServiceUserを取得
            var user = Get();
            // 未ログインであれば終了
            if (user == null)
            {
                return;
            }

            // MobileServiceUserをセット
            TodoItemManager.DefaultManager.CurrentClient.CurrentUser = user;
        }

        /// <summary>
        /// サインイン済かどうか
        /// </summary>
        /// <returns></returns>
        public static bool IsAuthenticated()
        {
            return TodoItemManager.DefaultManager.CurrentClient.CurrentUser != null;
        }
    }
}

標準のMicrosoft.WindowsAzure.MobileServicesではおそらく用意されていない、Account保持や、トークン更新に関する処理を、このクラスで定義しています。

次に、IAuthenticateインタフェースを定義します。

App.cs
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;

namespace xamarin_auth_1st
{
    /// <summary>
    /// Authentication Interface
    /// </summary>
    public interface IAuthenticate
    {
        Task<bool> AuthenticateAsync(MobileServiceAuthenticationProvider provider);
    }  
    public class App : Application
    {
        // 略

        // 追加
        public static IAuthenticate Authenticator { get; private set; }
        public static void Init(IAuthenticate authenticator)
        {
            Authenticator = authenticator;
            //AccountStoreからMobileServiceUserにセット
            Xamarin.Auth.AccountHelper.OnInitialized();
        }
    }
}

公式ドキュメントだと、AuthenticateAsyncは引数を持っていないのですが、これだと1つのプロバイダにしか対応できないので、列挙体MobileServiceAuthenticationProviderを引数としています。

次に、TodoItemManagerを修正します。

TodoItemManager.cs
namespace xamarin_auth_1st
{
    public partial class TodoItemManager
    {
        public async Task<ObservableCollection<TodoItem>> GetTodoItemsAsync(bool syncItems = false)
        {
            try
            {
                // トークン最新化
                Xamarin.Auth.AccountHelper.Refresh();

                // 略
            }
         }

        public async Task SaveTaskAsync(TodoItem item)
        {
            // トークン最新化
            Xamarin.Auth.AccountHelper.Refresh();

            // 略
        }
    }
}

Mobile Appsと通信を行う前に、トークンのRefreshを行い、期限切れを防ぐようにしています。
MobileServiceClient.RefreshUserAsync()という関数もあるのですが、どうにもうまくいかないことが多かったので、自分はHttpClient使っています。

次に、サインインボタンを追加します。

TodoList.xaml
<Button x:Name="loginButton_aad" Text="Sign-in AAD" MinimumHeightRequest="30"
     Clicked="loginButton_aad_Clicked"/>
<Button x:Name="loginButton_google" Text="Sign-in google" MinimumHeightRequest="30"
     Clicked="loginButton_google_Clicked"/>
<Button x:Name="logoutButton" Text="Sign-out" MinimumHeightRequest="30"
     Clicked="logoutButton_Clicked"/>

このXMLを、適当な場所に追加して下さい。

TodoList.xaml.cs
using Xamarin.Auth;
using Microsoft.WindowsAzure.MobileServices;

namespace xamarin_auth_1st
{
    public partial class TodoList : ContentPage
    {
        // 追加
        bool authenticated
        {
            get
            {
                return AccountHelper.IsAuthenticated();
            }
        }

        public TodoList()
        {
            InitializeComponent();

            // 略

            // 追加。AccountStoreからMobileServiceUserをセット
            AccountHelper.OnInitialized();
        }

        // 略

        private async Task RefreshItems(bool showActivityIndicator, bool syncItems)
        {
            // サインインボタンの表示非表示の切り替え
            this.FindByName<Button>("loginButton_aad").IsVisible = !authenticated;
            this.FindByName<Button>("loginButton_google").IsVisible = !authenticated;
            this.FindByName<Button>("logoutButton").IsVisible = authenticated;
            // サインイン済であれば、データ取得
            if (authenticated)
            {
                using (var scope = new ActivityIndicatorScope(syncIndicator, showActivityIndicator))
                {
                    todoList.ItemsSource = await manager.GetTodoItemsAsync(syncItems);
                }
            }
            // 未サインインであれば、クリア
            else
            {
                (todoList.ItemsSource as System.Collections.ObjectModel.ObservableCollection<TodoItem>)?.Clear();
            }
        }

        // 追加
        async Task login(MobileServiceAuthenticationProvider provider)
        {
            if (App.Authenticator != null)
                authenticated = await App.Authenticator.AuthenticateAsync(provider);

            // Set syncItems to true to synchronize the data on startup when offline is enabled.
            if (authenticated == true)
            {
                // サインイン者の情報をセット
                AccountHelper.Set(TodoItemManager.DefaultManager.CurrentClient.CurrentUser);
                await RefreshItems(true, syncItems: false);
            }
        }
        async void loginButton_aad_Clicked(object sender, EventArgs e)
        {
            await login(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory);
        }
        async void loginButton_google_Clicked(object sender, EventArgs e)
        {
            await login(MobileServiceAuthenticationProvider.Google);
        }

        async void logoutButton_Clicked(object sender, EventArgs e)
        {
            AccountHelper.Clear();
            await TodoItemManager.DefaultManager.CurrentClient.LogoutAsync();
            await RefreshItems(true, false);
        }
        // 略
    }
}
        

色々修正しましたが、やっていることは主に以下の内容です。
・読み込み時、MobileServiceUserのサインイン状態取得
・RefreshItems時、サインイン状態に従って、サインインのボタンの表示非表示を切り替える
 また、サインイン済であればリスト取得、そうでなければリスト削除
・サインインボタン押下時、AuthenticateAsync(各プラットフォーム側で実装)を実行
・サインイン完了したら、AccountStoreにサインイン情報を保持してRefreshItems
・サインアウトした時、AccountStoreからサインイン情報を削除してRefreshItems

PCLの修正は以上です。

Android修正

続いてAndroid側の修正を行います。

MainActivity.cs
using System.Threading.Tasks;
namespace xamarin_auth_1st.Droid
{
	public class MainActivity : FormsApplicationActivity, IAuthenticate
	{
		protected override void OnCreate (Bundle bundle)
		{
			// 略

			// Load the main application
			LoadApplication (new App ());

			// 追加
			// Initialize the authenticator before loading the app.
			App.Init((IAuthenticate)this);
		}
        // 追加
        // Define a authenticated user.
        private MobileServiceUser user;
        public async Task<bool> AuthenticateAsync(MobileServiceAuthenticationProvider provider)
        {
            var success = false;
            var message = string.Empty;
            try
            {
                // Sign in with Facebook login using a server-managed flow.
                user = await TodoItemManager.DefaultManager.CurrentClient.LoginAsync(this, provider, "xamarin-auth-1st");
                if (user != null)
                {
                    message = string.Format("you are now signed-in as {0}.",
                        user.UserId);
                    success = true;
                }
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }

            // Display the success or failure message.
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.SetMessage(message);
            builder.SetTitle("Sign-in result");
            builder.Create().Show();

            return success;
        }
	}
}

MainActivity.csに、サインイン時の処理を記載していきます。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.sample.xamarin_auth_1st">
	<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="25" />
	<application android:label="xamarin_auth_1st" android:icon="@drawable/icon">
        <!-- 追加 -->
        <activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity" android:launchMode="singleTop" android:noHistory="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="xamarin-auth-1st" android:host="easyauth.callback" />
            </intent-filter>
        </activity>
        <!-- /追加 -->
	</application>
</manifest>

マニフェストファイルに、URLスキームの情報を記載します。
これで、Android側の実装は完了です。

iOS修正

すみません、調査中です。

まとめ

このような手順で、Xamarin.FormsとAzure Mobile Appsのサインイン処理実装が完了します。
実際にアプリを組む場合、おそらく(というか間違いなく)プロバイダから、ユーザー名やEメールアドレス、プロバイダのアクセストークン取得など行う必要あると思いますが、
その辺の解説はまた別途、機会があれば行いたいと思っています。

参考文献

https://docs.microsoft.com/ja-jp/azure/app-service-mobile/app-service-mobile-xamarin-forms-get-started-users
https://docs.microsoft.com/ja-jp/azure/app-service/app-service-mobile-how-to-configure-active-directory-authentication
https://docs.microsoft.com/ja-jp/azure/app-service/app-service-mobile-how-to-configure-google-authentication

(宣伝1)Vietnam Japan Cloud Developer 2017 開催!

c22.png
https://connpass.com/event/67792/
Xamarin.FormsやAzure Mobile Appsをはじめとした、クラウドサービスのセミナーをハノイで開催します!
日程:2017/11/17(Fri) 16:00-20:00
自分も少し話します。英語か日本語かは要検討。
内容はYouTubeにも公開します。ハノイには来れない、という方もぜひYouTubeにてお越し下さい!

(宣伝2)Xamarin.Forms + Azure Mobile AppsのOSSプロジェクト開発中

現在、Xamarin.Forms + Azure Mobile AppsのOSSアプリを走らせています。
日々の日報などをSPのみで完結出来るアプリです。
GitHubで公開しておりますので、よければぜひご覧ください!
https://github.com/exceedone/APO-Chan

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?