.NET MAUI アプリに認証を追加する

このチュートリアルでは、Microsoft Entra ID を使用して、TodoApp プロジェクトに Microsoft 認証を追加します。 このチュートリアルを完了する前に、プロジェクトを作成し、バックエンドをデプロイしていることを確認してください。

ヒント

ここでは認証に Microsoft Entra ID を使用しますが、Azure Mobile Apps では任意の認証ライブラリを使用できます。

バックエンド サービスに認証を追加する

バックエンド サービスは標準の ASP.NET 6 サービスです。 ASP.NET 6 サービスで認証を有効にする方法を示すチュートリアルはすべて、Azure Mobile Apps にも適用できます。

バックエンド サービスに対して Microsoft Entra 認証を有効にするには、次の手順を実行する必要があります。

  • Microsoft Entra ID でアプリケーションを登録します。
  • ASP.NET 6 バックエンド プロジェクトに認証チェックを追加します。

アプリケーションを登録する

まず、Microsoft Entra テナントに Web API を登録し、次の手順に従ってスコープを追加します:

  1. Azure portal にサインインします。

  2. 複数のテナントにアクセスできる場合は、トップ メニューの [ディレクトリとサブスクリプション] フィルターを使用して、アプリケーションを登録するテナントに切り替えます。

  3. Microsoft Entra ID を検索して選択します。

  4. [管理][アプリの登録]>[新規登録] の順に選択します。

    • 名前: 登録するアプリケーションの名前を入力します。たとえば、TodoApp Quickstart など。 このアプリのユーザーには、この名前が表示されます。 これは後で変更できます。
    • [サポートされているアカウントの種類]: [任意の組織のディレクトリ (Microsoft Entra ID ディレクトリ - マルチテナント) 内のアカウントと、個人用の Microsoft アカウント (Skype、Xbox など)] を選択します。
  5. 登録 を選択します。

  6. [管理] で、 [API の公開]>[スコープの追加] の順に選択します。

  7. [アプリケーション ID URI] は既定値のままにして [保存して続行] を選択します。

  8. 次の詳細情報を入力します:

    • スコープ名: access_as_user
    • 同意できるのはだれですか? : 管理者とユーザー
    • 管理者の同意の表示名: Access TodoApp
    • 管理者の同意の説明: Allows the app to access TodoApp as the signed-in user.
    • ユーザーの同意の表示名: Access TodoApp
    • ユーザーの同意の説明: Allow the app to access TodoApp on your behalf.
    • [状態] :有効
  9. [スコープの追加] を選択してスコープの追加を完了します。

  10. api://<client-id>/access_as_user のようなスコープの値を書き留めます (Web API スコープ と呼ばれます)。 このスコープは、クライアントを構成するときに必要になります。

  11. [概要] を選択します。

  12. [Essentials] セクションの [アプリケーション (クライアント) ID] を書き留めます (Web API アプリケーション ID と呼ばれます)。 この値は、バックエンド サービスを構成するときに必要になります。

Visual Studio を開いて、TodoAppService.NET6 プロジェクトを選択します。

  1. TodoAppService.NET6 プロジェクトを右クリックし、[NuGet パッケージの管理] を選択します。

  2. 新しいタブで [参照] を選択し、検索ボックスに「Microsoft.Identity.Web」と入力します。

    Screenshot of adding the M S A L NuGet in Visual Studio.

  3. Microsoft.Identity.Web パッケージを選択し、[インストール] を押します。

  4. 指示に従ってパッケージのインストールを完了します。

  5. Program.cs を開きます。 using ステートメントの一覧に以下を追加します。

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
  1. builder.Services.AddDbContext() の呼び出しのすぐ上に次のコードを追加します。
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization();
  1. app.MapControllers() の呼び出しのすぐ上に次のコードを追加します。
app.UseAuthentication();
app.UseAuthorization();

Program.cs は次のようになります。

using Microsoft.AspNetCore.Datasync;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using TodoAppService.NET6.Db;
  
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
  
if (connectionString == null)
{
  throw new ApplicationException("DefaultConnection is not set");
}
  
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization();
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatasyncControllers();
  
var app = builder.Build();
  
// Initialize the database
using (var scope = app.Services.CreateScope())
{
  var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
  await context.InitializeDatabaseAsync().ConfigureAwait(false);
}
  
// Configure and run the web service.
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
  1. Controllers\TodoItemController.cs を編集します。 クラスに [Authorize] 属性を追加します。 クラスは次のようになります。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Datasync;
using Microsoft.AspNetCore.Datasync.EFCore;
using Microsoft.AspNetCore.Mvc;
using TodoAppService.NET6.Db;

namespace TodoAppService.NET6.Controllers
{
  [Authorize]
  [Route("tables/todoitem")]
  public class TodoItemController : TableController<TodoItem>
  {
    public TodoItemController(AppDbContext context)
      : base(new EntityTableRepository<TodoItem>(context))
    {
    }
  }
}
  1. appsettings.json を編集します。 次のブロックを追加します。
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com",
    "ClientId": "<client-id>",
    "TenantId": "common"
  },

<client-id> を、前に記録した "Web API アプリケーション ID" に置き換えます。 完了すると、次のようになります。

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com",
    "ClientId": "<client-id>",
    "TenantId": "common"
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=TodoApp;Trusted_Connection=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

サービスを Azure に再発行します。

  1. TodoAppService.NET6 プロジェクトを右クリックし、[パブリッシュ] を選択します。
  2. タブの右上隅にある [パブリッシュ] ボタンを選択します。

https://yoursite.azurewebsites.net/tables/todoitem?ZUMO-API-VERSION=3.0.0 を指定してブラウザーを開きます。 ここで、このサービスは 401 の応答を返すことに注意して下さい。これは、認証が必要であることを示しています。

Screenshot of the browser showing an error.

アプリを ID サービスに登録する

Microsoft Data sync Framework には、HTTP トランザクションのヘッダー内で JSON Web トークン (JWT) を使用する認証プロバイダーのサポートが組み込まれています。 このアプリケーションでは、Microsoft 認証ライブラリ (MSAL) を使用してそのようなトークンを要求し、サインインしているユーザーをバックエンド サービスに対して承認します。

ネイティブ クライアント アプリケーションを構成する

Microsoft ID ライブラリ (MSAL) などのクライアント ライブラリを使用してアプリでホストされている Web API への認証を許可するように、ネイティブ クライアントを登録できます。

  1. Azure portal で、[Microsoft Entra ID]>[アプリの登録]>[新規登録] の順に選択します。

  2. [アプリケーションの登録] ページで、次のようにします。

    • アプリケーション登録用の[名前]を入力します。 バックエンド サービスで使用される名前と区別するために、名前は native-quickstart の使用をお勧めします。
    • 任意の組織のディレクトリ (Microsoft Entra ディレクトリ - マルチテナント) 内のアカウントと、個人用の Microsoft アカウント (Skype、Xbox など) を選択します。
    • [リダイレクト URI] は次のようにします。
      • [パブリック クライアント (モバイル & デスクトップ)] を選択します。
      • URL quickstart://auth を入力します。
  3. 登録 を選択します。

  4. [API のアクセス許可][アクセス許可の追加][自分の API] の順に選択します。

  5. 以前にバックエンド サービス用に作成したアプリ登録を選択します。 アプリ登録が表示されない場合は、access_as_user スコープを追加しているかを確認します。

    Screenshot of the scope registration in the Azure portal.

  6. [アクセス許可の選択] で [access_as_user] を選択してから、[アクセス許可の追加] を選択します。

  7. [認証] > [モバイル アプリケーションとデスクトップ アプリケーション] の順に選択します。

  8. https://login.microsoftonline.com/common/oauth2/nativeclient の横にあるチェック ボックスをオンにします。

  9. msal{client-id}://auth の横にあるチェック ボックスをオンにします ({client-id} をアプリケーション ID に置き換える)。

  10. [URI の追加] を選択し、追加 URI のフィールドに http://localhost を追加します。

  11. ページの下部にある [保存] を選択します。

  12. [概要] を選択します。 [アプリケーション (クライアント) ID] を書き留めます (ネイティブ クライアント アプリケーション ID と呼ばれます)。これは、モバイル アプリを構成するときに必要になります。

次の 3 つのリダイレクト URL を定義しました。

  • http://localhost は WPF アプリケーションで使用されます。
  • https://login.microsoftonline.com/common/oauth2/nativeclient は UWP アプリケーションで使用されます。
  • msal{client-id}://auth は、モバイル (Android および iOS) アプリケーションで使用されます。

Microsoft Identity Client をアプリに追加する

TodoApp.sln ソリューションを Visual Studio で開き、TodoApp.MAUI プロジェクトをスタートアップ プロジェクトとして設定します。 Microsoft Identity Library (MSAL)TodoApp.MAUI プロジェクトに追加します。

Microsoft Identity Library (MSAL) をプラットフォーム プロジェクトに追加します。

  1. プロジェクトを右クリックし、[NuGet パッケージの管理] を選択します。

  2. [参照] タブを選択します。

  3. 検索ボックスに「Microsoft.Identity.Client」と入力し、Enter キーを押します。

  4. Microsoft.Identity.Client の結果を選択し、[インストール] をクリックします。

    Screenshot of selecting the MSAL NuGet in Visual Studio.

  5. 使用許諾契約書に同意してインストールを続行します。

ネイティブ クライアント ID とバックエンド スコープを構成に追加します。

TodoApp.Data プロジェクトを開き、Constants.cs ファイルを編集します。 ApplicationIdScopes の定数を追加します。

  public static class Constants
  {
      /// <summary>
      /// The base URI for the Datasync service.
      /// </summary>
      public static string ServiceUri = "https://demo-datasync-quickstart.azurewebsites.net";

      /// <summary>
      /// The application (client) ID for the native app within Microsoft Entra ID
      /// </summary>
      public static string ApplicationId = "<client-id>";

      /// <summary>
      /// The list of scopes to request
      /// </summary>
      public static string[] Scopes = new[]
      {
          "<scope>"
      };
  }

<client-id> を、クライアント アプリケーションを Microsoft Entra ID に登録するときに受け取ったネイティブ クライアント アプリケーション ID に置き換えます。次に、<scope> を、サービス アプリケーションの登録時に [API の公開] を使用したときにコピーした Web API スコープ に置き換えます。

TodoApp.MAUI プロジェクトで MainPage.xaml.cs クラスを開きます。 次の using ステートメントを追加します。

using Microsoft.Datasync.Client;
using Microsoft.Identity.Client;
using System.Diagnostics;

MainPage クラスで、新しいプロパティを追加します。

public IPublicClientApplication IdentityClient { get; set; }

読み取るコンストラクターを調整します。

public MainPage()
{
    InitializeComponent();
    TodoService = new RemoteTodoService(GetAuthenticationToken);
    viewModel = new MainViewModel(this, TodoService);
    BindingContext = viewModel;
}

GetAuthenticationToken メソッドをクラスに追加します。

public async Task<AuthenticationToken> GetAuthenticationToken()
{
    if (IdentityClient == null)
    {
#if ANDROID
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithRedirectUri($"msal{Constants.ApplicationId}://auth")
            .WithParentActivityOrWindow(() => Platform.CurrentActivity)
            .Build();
#elif IOS
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithIosKeychainSecurityGroup("com.microsoft.adalcache")
            .WithRedirectUri($"msal{Constants.ApplicationId}://auth")
            .Build();
#else
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
            .Build();
#endif
    }

    var accounts = await IdentityClient.GetAccountsAsync();
    AuthenticationResult result = null;
    bool tryInteractiveLogin = false;

    try
    {
        result = await IdentityClient
            .AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault())
            .ExecuteAsync();
    }
    catch (MsalUiRequiredException)
    {
        tryInteractiveLogin = true;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"MSAL Silent Error: {ex.Message}");
    }

    if (tryInteractiveLogin)
    {
        try
        {
            result = await IdentityClient
                .AcquireTokenInteractive(Constants.Scopes)
                .ExecuteAsync();
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"MSAL Interactive Error: {ex.Message}");
        }
    }

    return new AuthenticationToken
    {
        DisplayName = result?.Account?.Username ?? "",
        ExpiresOn = result?.ExpiresOn ?? DateTimeOffset.MinValue,
        Token = result?.AccessToken ?? "",
        UserId = result?.Account?.Username ?? ""
    };
}

GetAuthenticationToken() メソッドは Microsoft Identity Library (MSAL) と連携して、サインインしているユーザーのバックエンド サービスへの認証に適したアクセス トークンを取得します。 この関数は、クライアント作成のために RemoteTodoService に渡されます。 認証が成功すると、各要求の認証に必要なデータと共に AuthenticationToken が生成されます。 成功しなかった場合は、代わりに期限切れの無効なトークンが生成されます。

プラットフォーム固有のオプションを追加するには、#if 領域にプラットフォーム指定子を使用します。 たとえば、Android では、呼び出し元のページから渡される親アクティビティを指定する必要があります。

認証用に Android アプリを構成する

次のコードで新しいクラス Platforms\Android\MsalActivity.cs を作成します。

using Android.App;
using Android.Content;
using Microsoft.Identity.Client;

namespace TodoApp.MAUI
{
    [Activity(Exported = true)]
    [IntentFilter(new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
        DataHost = "auth",
        DataScheme = "msal{client-id}")]
    public class MsalActivity : BrowserTabActivity
    {
    }
}

{client-id} をネイティブ クライアントのアプリケーション ID に置き換えます (これは Constants.ApplicationId と同じです)。

プロジェクトが Android バージョン 11 (API バージョン 30) 以降を対象としている場合は、Android パッケージの可視性要件を満たすように AndroidManifest.xml を更新する必要があります。 Platforms/Android/AndroidManifest.xml を開き、次の queries/intent ノードを manifest ノードに追加します。

<manifest>
  ...
  <queries>
    <intent>
      <action android:name="android.support.customtabs.action.CustomTabsService" />
    </intent>
  </queries>
</manifest>

MauiProgram.cs を開きます。 ファイルの先頭に、次の using ステートメントを含めます。

using Microsoft.Identity.Client;

ビルダーを次のコードで更新します。

    builder
        .UseMauiApp<App>()
        .ConfigureLifecycleEvents(events =>
        {
#if ANDROID
            events.AddAndroid(platform =>
            {
                platform.OnActivityResult((activity, rc, result, data) =>
                {
                    AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(rc, result, data);
                });
            });
#endif
        })
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
        });

iOS 用アプリケーションをアップデートした後にこの手順を実行する場合は、#if ANDROID で指定されたコード (#if および #endif を含む) を追加します。 コンパイラは、コンパイル対象のプラットフォームに基づいて正しいコード部分を選択します。 このコードは、iOS 用の既存のブロックの前または後に配置できます。

Android で認証が必要になると、ID クライアントの取得後、システム ブラウザーを開く内部アクティビティに切り替わります。 認証が完了すると、システム ブラウザーでは定義済みのリダイレクト URL (msal{client-id}://auth) にリダイレクトします。 MsalActivity はリダイレクト URL をトラップし、OnActivityResult() の呼び出しによりメイン アクティビティに戻ります。 OnActivityResult() メソッドは、MSAL 認証ヘルパーを呼び出してトランザクションを完了します。

Android アプリをテストする

TodoApp.MAUI をスタートアップ プロジェクトとして設定し、Android エミュレーターをターゲットとして選択し、F5 キーを押してアプリをビルドして実行します。 アプリが起動すると、ユーザーはアプリにサインインするように求められます。 最初の実行時に、ユーザーはアプリへの同意を求められます。 認証が完了すると、アプリは通常どおりに実行されます。

Windows アプリをテストする

TodoApp.MAUI をスタートアップ プロジェクトとして設定し、Windows コンピューターをターゲットとして選択し、F5 キーを押してアプリをビルドして実行します。 アプリが起動すると、ユーザーはアプリにサインインするように求められます。 最初の実行時に、ユーザーはアプリへの同意を求められます。 認証が完了すると、アプリは通常どおりに実行されます。

次のステップ

次に、オフライン ストアを実装して、オフラインで動作するようにアプリケーションを構成します。

参考資料