.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 테넌트에 웹 API를 등록하고 다음 단계를 수행하여 범위를 추가합니다.

  1. Azure Portal에 로그인합니다.

  2. 여러 테넌트에 액세스할 수 있는 경우 위쪽 메뉴에서 디렉터리 + 구독 필터를 사용하여 애플리케이션을 등록하려는 테넌트로 전환합니다.

  3. Microsoft Entra ID를 검색하여 선택합니다.

  4. 관리 아래에서 앱 등록>새 등록을 선택합니다.

    • 이름: 애플리케이션의 이름(예 : TodoApp 빠른 시작)을 입력합니다. 앱 사용자에게 이 이름이 표시됩니다. 나중에 변경할 수 있습니다.
    • 지원되는 계정 유형: 모든 조직 디렉터리의 계정(모든 Microsoft Entra 디렉터리 - 다중 테넌트) 및 개인 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. 범위 값(Web API 범위라고 함)과 유사합니다 api://<client-id>/access_as_user . 클라이언트를 구성할 때 범위가 필요합니다.

  11. 개요를 선택합니다.

  12. Essentials 섹션(Web API 애플리케이션 ID 라고 함)의 애플리케이션(클라이언트) 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 패키지를 선택한 다음 Install 키를 누릅니.

  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 데이터 동기화 프레임워크는 HTTP 트랜잭션의 헤더 내에서 JWT(Json Web Token)를 사용하는 모든 인증 공급자를 기본적으로 지원합니다. 이 애플리케이션은 MSAL(Microsoft 인증 라이브러리) 을 사용하여 이러한 토큰을 요청하고 로그인한 사용자에게 백 엔드 서비스에 권한을 부여합니다.

네이티브 클라이언트 애플리케이션 구성

MSAL(Microsoft Identity Library)과 같은 클라이언트 라이브러리를 사용하여 앱에서 호스트되는 웹 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 확인란을 선택합니다(애플리케이션 ID로 바꾸기 {client-id} ).

  10. URI 추가를 선택한 다음, 필드에 추가 URI를 추가 http://localhost 합니다.

  11. 페이지 아래쪽에서 저장을 선택합니다.

  12. 개요를 선택합니다. 모바일 앱을 구성하는 데 필요한 애플리케이션(클라이언트) ID(네이티브 클라이언트 애플리케이션 ID라고 함)를 기록해 둡니다.

세 가지 리디렉션 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. 프로젝트에 MSAL(Microsoft Identity Library)TodoApp.MAUI 추가합니다.

플랫폼 프로젝트에 MSAL(Microsoft Identity Library)을 추가합니다.

  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에 등록할 때 받은 Native Client 애플리케이션 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() MSAL(Microsoft Identity Library)과 함께 작동하여 로그인한 사용자에게 백 엔드 서비스에 권한을 부여하는 데 적합한 액세스 토큰을 가져옵니다. 그런 다음 이 함수는 클라이언트를 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
    {
    }
}

네이티브 클라이언트의 애플리케이션 ID(예: 동일)로 Constants.ApplicationId바꿉 {client-id} 니다.

프로젝트가 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#endif포함)에 의해 #if ANDROID 지정된 코드를 추가합니다. 컴파일러는 컴파일 중인 플랫폼에 따라 올바른 코드 조각을 선택합니다. 이 코드는 iOS용 기존 블록 앞이나 뒤를 배치할 수 있습니다.

Android에서 인증이 필요한 경우 ID 클라이언트를 가져온 다음 시스템 브라우저를 여는 내부 활동으로 전환합니다. 인증이 완료되면 시스템 브라우저가 정의된 리디렉션 URL(msal{client-id}://auth)로 리디렉션됩니다. 리디렉션 MsalActivity URL을 트래핑한 다음 호출OnActivityResult()하여 기본 활동으로 다시 전환합니다. 이 메서드는 OnActivityResult() MSAL 인증 도우미를 호출하여 트랜잭션을 완료합니다.

Android 앱 테스트

시작 프로젝트로 설정하고TodoApp.MAUI, 대상으로 Android 에뮬레이터를 선택한 다음, F5 키를 눌러 앱을 빌드하고 실행합니다. 앱이 시작되면 앱에 로그인하라는 메시지가 표시됩니다. 첫 번째 실행에서는 앱에 동의하라는 메시지가 표시됩니다. 인증이 완료되면 앱이 정상적으로 실행됩니다.

Windows 앱 테스트

시작 프로젝트로 설정하고 TodoApp.MAUI 대상으로 Windows Machine을 선택한 다음 F5 키를 눌러 앱을 빌드하고 실행합니다. 앱이 시작되면 앱에 로그인하라는 메시지가 표시됩니다. 첫 번째 실행에서는 앱에 동의하라는 메시지가 표시됩니다. 인증이 완료되면 앱이 정상적으로 실행됩니다.

다음 단계

다음으로 오프라인 저장소를 구현하여 오프라인으로 작동하도록 애플리케이션을 구성합니다.

추가 참고 자료