다음을 통해 공유


ASP.NET Core Blazor WebAssembly에서 Graph API 사용

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

이 문서에서는 앱에서 Microsoft Graph를 사용하여 앱이 Blazor WebAssembly Microsoft 클라우드 리소스에 액세스할 수 있도록 하는 방법을 설명합니다.

다음 두 가지 방법을 다룹니다.

  • Graph SDK: Microsoft Graph SDKMicrosoft Graph에 액세스하는 고품질의 효율적이고 복원력 있는 앱 빌드를 간소화합니다. 이 방법을 채택하려면 이 문서의 맨 위에 있는 Graph SDK 단추를 선택합니다.

  • Graph API로 명명된 HttpClient: 명명된 이름은 HttpClient Microsoft Graph에 직접 Microsoft Graph API 요청을 실행할 수 있습니다. 이 방법을 채택하려면 이 문서의 맨 위에 있는 알려진 Graph API HttpClient 단추를 선택합니다.

이 문서의 지침은 다른 Microsoft 설명서 집합의 Microsoft Graph 설명서 및 Azure 보안 지침을 대체하기 위한 것이 아닙니다. 프로덕션 환경에서 Microsoft Graph를 구현하기 전에 이 문서의 추가 리소스 섹션에서 보안 지침을 평가합니다. Microsoft의 모범 사례를 따라 앱의 취약성을 제한합니다.

Microsoft Graph를 사용하기 위한 추가 접근 방식과 Blazor WebAssembly 다음 Microsoft Graph 및 Azure 샘플에서 제공됩니다.

위의 두 샘플 중 하나에 대한 피드백을 제공하려면 샘플의 GitHub 리포지토리에서 문제를 엽니다. Azure 샘플에 대한 문제를 여는 경우 Azure 샘플 리포지토리(Azure-Samples)에 많은 샘플이 포함되어 있으므로 여는 주석에서 샘플에 대한 링크를 제공합니다. 문제를 자세히 설명하고 필요에 따라 샘플 코드를 포함합니다. 문제 또는 오류를 재현하는 최소 앱을 GitHub에 배치합니다. 퍼블릭 리포지토리 커밋하기 전에 샘플에서 Azure 계정 구성 데이터를 제거해야 합니다.

피드백을 제공하거나 이 문서 또는 ASP.NET Core에 대한 지원을 요청하려면 ASP.NET Core Blazor 기본 사항을 참조하세요.

Important

이 문서에 설명된 시나리오는 Microsoft Entra(ME-ID)를 AAD B2C가 아닌 공급자로 identity 사용하는 데 적용됩니다. 클라이언트 쪽 Blazor WebAssembly 앱 및 AAD B2C identity 공급자와 함께 Microsoft Graph를 사용하는 것은 현재 지원되지 않습니다. 앱에는 클라이언트 쪽 Blazor 앱에서 보안을 설정할 수 없는 클라이언트 암호가 필요하기 때문입니다. AAD B2C 독립 실행형 Blazor WebAssembly 앱의 경우 Graph API를 사용하여 사용자를 대신하여 Graph API에 액세스하는 백 엔드 서버(웹) API를 만듭니다. 클라이언트 쪽 앱은 사용자가 웹 API를 호출하여 Microsoft Graph에 안전하게 액세스하고 서버 기반 웹 API 에서 클라이언트 쪽 Blazor 앱으로 데이터를 반환하도록 인증하고 권한을 부여합니다. 클라이언트 암호는 클라이언트의 앱이 아니라 서버 기반 웹 API에서 Blazor 안전하게 유지 관리됩니다. 클라이언트 쪽 Blazor 앱에 클라이언트 비밀을 저장하지 마세요.

호스트된 Blazor WebAssembly 앱 사용이 지원됩니다. 여기서 Server 앱은 Graph SDK/API를 사용하여 웹 API를 통해 Client 앱에 그래프 데이터를 제공합니다. 자세한 내용은 이 문서의 호스트된 Blazor WebAssembly 솔루션 섹션을 참조하세요.

이 문서의 예제에서는 새로운 .NET/C# 기능을 활용합니다. .NET 7 이전 버전에서 예제를 사용하는 경우 약간의 수정이 필요합니다. 그러나 Microsoft Graph와의 상호 작용과 관련된 텍스트 및 코드 예제는 모든 버전의 ASP.NET Core에 대해 동일합니다.

다음 지침은 Microsoft Graph v5에 적용됩니다.

앱에서 Blazor 사용할 Microsoft Graph SDK를 Microsoft Graph .NET 클라이언트 라이브러리라고 합니다.

Graph SDK 예제에는 독립 실행형 Blazor WebAssembly 앱에서 다음 패키지 참조가 필요합니다. 앱이 MSAL 인증에 사용하도록 설정된 경우(예: Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안의 지침에 따라 앱을 만들 때) 처음 두 패키지가 이미 참조되어 있습니다.

Graph SDK 예제에는 독립 실행형 Blazor WebAssembly 앱 또는 Client 호스트 Blazor WebAssembly 된 솔루션의 앱에서 다음 패키지 참조가 필요합니다. 앱이 MSAL 인증에 사용하도록 설정된 경우(예: Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안의 지침에 따라 앱을 만들 때) 처음 두 패키지가 이미 참조되어 있습니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Azure Portal에서 앱이 사용자를 대신하여 액세스할 수 있어야 하는 Microsoft Graph 데이터에 대해 위임된 권한(범위)† 부여합니다. 이 문서의 예제에서 앱 등록에는 사용자 데이터를 읽을 수 있는 위임된 권한(Microsoft.Graph>User.ReadAPI 권한범위, 형식: 위임됨)이 포함되어야 합니다. 이 User.Read 범위를 통해 사용자는 앱에 로그인할 수 있으며 앱에서 로그인한 사용자의 프로필 및 회사 정보를 읽을 수 있습니다. 자세한 내용은 Microsoft 플랫폼의 사용 권한 및 동의 개요 및 Microsoft identity Graph 권한 개요를 참조하세요.

†사용범위는 동일한 것을 의미하며 보안 설명서 및 Azure Portal에서 서로 바꿔서 사용됩니다. 텍스트가 Azure Portal을 참조하지 않는 한 이 문서에서는 Graph 권한을 참조할 때 범위/범위를 사용합니다.

범위는 대/소문자를 구분하지 않으므로 User.Read user.read. 두 형식 중 하나를 자유롭게 사용할 수 있지만 애플리케이션 코드에서 일관된 선택을 하는 것이 좋습니다.

Azure Portal에서 앱 등록에 Microsoft Graph API 범위를 추가한 후 앱의 파일에 다음 앱 설정 구성을 wwwroot/appsettings.json 추가합니다. 여기에는 Microsoft Graph 버전 및 범위가 포함된 그래프 기본 URL이 포함됩니다. 다음 예제에서는 이 문서의 이후 섹션에 나오는 예제에 대해 User.Read 범위를 지정합니다. 범위는 대/소문자를 구분하지 않습니다.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

앞의 예제 {VERSION} 에서 자리 표시자는 Microsoft Graph API의 버전입니다(예: v1.0).

다음은 ME-ID를 공급자로 identity 사용하는 앱에 대한 전체 wwwroot/appsettings.json 구성 파일의 예입니다. 여기서 Microsoft Graph에 대해 사용자 데이터(user.read범위)를 읽습니다.

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

앞의 예제 {TENANT ID} 에서 자리 표시자는 디렉터리(테넌트) ID이고 {CLIENT ID} 자리 표시자는 애플리케이션(클라이언트) ID입니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

독립 실행형 앱에 다음 GraphClientExtensions 클래스를 추가합니다. 범위는 AuthenticateRequestAsync 메서드에서 AccessTokenRequestOptionsScopes 속성에 제공됩니다.

독립 실행형 앱 또는 호스트된 Blazor WebAssembly솔루션Client 앱에 다음 GraphClientExtensions 클래스를 추가합니다. 범위는 AuthenticateRequestAsync 메서드에서 AccessTokenRequestOptionsScopes 속성에 제공됩니다.

액세스 토큰을 얻지 못하면 다음 코드는 Graph 요청에 대한 전달자 권한 부여 헤더를 설정하지 않습니다.

GraphClientExtensions.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using IAccessTokenProvider = 
    Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider;

namespace BlazorSample;

internal static class GraphClientExtensions
{
    public static IServiceCollection AddGraphClient(
            this IServiceCollection services, string? baseUrl, List<string>? scopes)
    {
        if (string.IsNullOrEmpty(baseUrl) || scopes?.Count == 0)
        {
            return services;
        }

        services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(
            options =>
            {
                scopes?.ForEach((scope) =>
                {
                    options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
                });
            });

        services.AddScoped<IAuthenticationProvider, GraphAuthenticationProvider>();

        services.AddScoped(sp =>
        {
            return new GraphServiceClient(
                new HttpClient(),
                sp.GetRequiredService<IAuthenticationProvider>(),
                baseUrl);
        });

        return services;
    }

    private class GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, 
        IConfiguration config) : IAuthenticationProvider
    {
        private readonly IConfiguration config = config;

        public IAccessTokenProvider TokenProvider { get; } = tokenProvider;

        public async Task AuthenticateRequestAsync(RequestInformation request, 
            Dictionary<string, object>? additionalAuthenticationContext = null, 
            CancellationToken cancellationToken = default)
        {
            var result = await TokenProvider.RequestAccessToken(
                new AccessTokenRequestOptions()
                {
                    Scopes = 
                        config.GetSection("MicrosoftGraph:Scopes").Get<string[]>() ??
                        [ "user.read" ]
                });

            if (result.TryGetToken(out var token))
            {
                request.Headers.Add("Authorization", 
                    $"{CoreConstants.Headers.Bearer} {token.Value}");
            }
        }
    }
}

Important

앞의 코드가 AdditionalScopesToConsentDefaultAccessTokenScopes 범위를 AdditionalScopesToConsent추가하는 데 사용하는 DefaultAccessTokenScopes 이유에 대한 설명은 대 섹션을 참조하세요.

파일에서 Program 확장 메서드를 사용하여 Graph 클라이언트 서비스 및 구성을 AddGraphClient 추가합니다. 다음 코드는 앱 설정 파일에서 이러한 설정을 찾을 수 없는 경우 버전 1.0 Microsoft Graph 기본 주소 및 User.Read 범위로 기본 설정됩니다.

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

Graph SDK를 사용하는 구성 요소에서 Graph API 호출

다음 UserData 구성 요소는 삽입을 GraphServiceClient 사용하여 사용자의 ME-ID 프로필 데이터를 가져오고 휴대폰 번호를 표시합니다.

ME-ID로 만든 테스트 사용자의 경우 Azure Portal에서 사용자의 ME-ID 프로필에 휴대폰 번호를 제공해야 합니다.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
    <p>Mobile Phone: @user.MobilePhone</p>
}

@code {
    private Microsoft.Graph.Models.User? user;

    protected override async Task OnInitializedAsync()
    {
        user = await Client.Me.GetAsync();
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

앱에 사용자를 추가하려면 앱 역할 유의 여부에 관계없이 앱 등록에 사용자 할당 섹션을 참조하세요.

Graph SDK를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

Graph SDK를 사용하여 사용자 클레임 사용자 지정

다음 예제에서 앱은 ME-ID 사용자 프로필의 데이터에서 사용자의 휴대폰 번호 및 사무실 위치 클레임을 만듭니다. 앱에는 ME-ID로 User.Read 구성된 Graph API 범위가 있어야 합니다. 이 시나리오에 대한 모든 테스트 사용자는 AZURE Portal을 통해 추가할 수 있는 ME-ID 프로필에 휴대폰 번호와 사무실 위치가 있어야 합니다.

다음 사용자 지정 사용자 계정 팩터리에서 다음이 적용됩니다.

  • CreateUserAsync 메서드에서 정보 또는 오류를 기록하려는 경우 편의를 위해 ILogger(logger)가 포함됩니다.
  • throw되는 AccessTokenNotAvailableException 경우 사용자는 공급자로 identity 리디렉션되어 계정에 로그인합니다. 액세스 토큰 요청이 실패할 때 추가 또는 다른 작업을 수행할 수 있습니다. 예를 들어 앱은 AccessTokenNotAvailableException을 로깅하고 추가 조사를 위해 지원 티켓을 만들 수 있습니다.
  • 프레임워크의 RemoteUserAccount은 사용자의 계정을 나타냅니다. 앱에 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스가 필요한 경우, 다음 코드에서 RemoteUserAccount에 대한 사용자 지정 사용자 계정 클래스를 바꿉니다.

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions.Authentication;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger,
        IConfiguration config) 
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;
    private readonly string? baseUrl = string.Join("/",
        config.GetSection("MicrosoftGraph")["BaseUrl"] ?? 
            "https://graph.microsoft.com",
        config.GetSection("MicrosoftGraph")["Version"] ??
            "v1.0");

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null &&
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null && !string.IsNullOrEmpty(baseUrl))
            {
                try
                {
                    var client = new GraphServiceClient(
                        new HttpClient(),
                        serviceProvider
                            .GetRequiredService<IAuthenticationProvider>(),
                        baseUrl);

                    var user = await client.Me.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

사용자 지정 사용자 계정 팩터리를 사용하도록 MSAL 인증을 구성합니다.

Program 파일이 Microsoft.AspNetCore.Components.WebAssembly.Authentication 네임스페이스를 사용하는지 확인합니다.

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

이 섹션의 예제는 파일의 섹션 wwwroot/appsettings.json 을 통해 앱 구성의 버전 및 범위를 사용하여 기본 URL을 읽는 MicrosoftGraph 방법을 기반으로 합니다. 이 문서의 앞부분에 있는 지침에 Program 따라 파일에 다음 줄이 이미 있어야 합니다.

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

파일에서 Program 확장 메서드에 대한 호출을 AddMsalAuthentication 찾습니다. CustomAccountFactory를 사용하여 계정 클레임 보안 주체 팩터리를 추가하는 에 대한 AddAccountClaimsPrincipalFactory 호출을 포함하는 코드를 다음으로 업데이트합니다.

앱에서 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스를 사용하는 경우 다음 코드에서 RemoteUserAccount에 대한 사용자 지정 사용자 계정 클래스를 바꿉니다.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount,
        CustomAccountFactory>();

다음 UserClaims 구성 요소를 사용하여 사용자가 ME-ID로 인증한 후 사용자의 클레임을 연구할 수 있습니다.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Graph SDK를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

다음 지침은 Microsoft Graph v4에 적용됩니다. 앱을 SDK v4에서 v5로 업그레이드하는 경우 Microsoft Graph .NET SDK v5 변경 로그 및 업그레이드 가이드를 참조 하세요.

앱에서 Blazor 사용할 Microsoft Graph SDK를 Microsoft Graph .NET 클라이언트 라이브러리라고 합니다.

Graph SDK 예제에는 독립 실행형 Blazor WebAssembly 앱에서 다음 패키지 참조가 필요합니다. 앱이 MSAL 인증에 사용하도록 설정된 경우(예: Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안의 지침에 따라 앱을 만들 때) 처음 두 패키지가 이미 참조되어 있습니다.

Graph SDK 예제에는 독립 실행형 Blazor WebAssembly 앱 또는 Client 호스트 Blazor WebAssembly 된 솔루션의 앱에서 다음 패키지 참조가 필요합니다. 앱이 MSAL 인증에 사용하도록 설정된 경우(예: Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안의 지침에 따라 앱을 만들 때) 처음 두 패키지가 이미 참조되어 있습니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Azure Portal에서 앱이 사용자를 대신하여 액세스할 수 있어야 하는 Microsoft Graph 데이터에 대해 위임된 권한(범위)† 부여합니다. 이 문서의 예제에서 앱 등록에는 사용자 데이터를 읽을 수 있는 위임된 권한(Microsoft.Graph>User.ReadAPI 권한범위, 형식: 위임됨)이 포함되어야 합니다. 이 User.Read 범위를 통해 사용자는 앱에 로그인할 수 있으며 앱에서 로그인한 사용자의 프로필 및 회사 정보를 읽을 수 있습니다. 자세한 내용은 Microsoft 플랫폼의 사용 권한 및 동의 개요 및 Microsoft identity Graph 권한 개요를 참조하세요.

†사용범위는 동일한 것을 의미하며 보안 설명서 및 Azure Portal에서 서로 바꿔서 사용됩니다. 텍스트가 Azure Portal을 참조하지 않는 한 이 문서에서는 Graph 권한을 참조할 때 범위/범위를 사용합니다.

범위는 대/소문자를 구분하지 않으므로 User.Read user.read. 두 형식 중 하나를 자유롭게 사용할 수 있지만 애플리케이션 코드에서 일관된 선택을 하는 것이 좋습니다.

Azure Portal에서 앱 등록에 Microsoft Graph API 범위를 추가한 후 앱의 파일에 다음 앱 설정 구성을 wwwroot/appsettings.json 추가합니다. 여기에는 Microsoft Graph 버전 및 범위가 포함된 그래프 기본 URL이 포함됩니다. 다음 예제에서는 이 문서의 이후 섹션에 나오는 예제에 대해 User.Read 범위를 지정합니다. 범위는 대/소문자를 구분하지 않습니다.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

앞의 예제 {VERSION} 에서 자리 표시자는 Microsoft Graph API의 버전입니다(예: v1.0).

다음은 ME-ID를 공급자로 identity 사용하는 앱에 대한 전체 wwwroot/appsettings.json 구성 파일의 예입니다. 여기서 Microsoft Graph에 대해 사용자 데이터(user.read범위)를 읽습니다.

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

앞의 예제 {TENANT ID} 에서 자리 표시자는 디렉터리(테넌트) ID이고 {CLIENT ID} 자리 표시자는 애플리케이션(클라이언트) ID입니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

독립 실행형 앱에 다음 GraphClientExtensions 클래스를 추가합니다. 범위는 AuthenticateRequestAsync 메서드에서 AccessTokenRequestOptionsScopes 속성에 제공됩니다. IHttpProvider.OverallTimeout는 기본값인 100초에서 300초로 확장되어 Microsoft Graph에서 HttpClient가 응답을 받는 데 더 많은 시간을 제공합니다.

독립 실행형 앱 또는 호스트된 Blazor WebAssembly솔루션Client 앱에 다음 GraphClientExtensions 클래스를 추가합니다. 범위는 AuthenticateRequestAsync 메서드에서 AccessTokenRequestOptionsScopes 속성에 제공됩니다. IHttpProvider.OverallTimeout는 기본값인 100초에서 300초로 확장되어 Microsoft Graph에서 HttpClient가 응답을 받는 데 더 많은 시간을 제공합니다.

액세스 토큰을 얻지 못하면 다음 코드는 Graph 요청에 대한 전달자 권한 부여 헤더를 설정하지 않습니다.

GraphClientExtensions.cs:

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;

namespace BlazorSample;

internal static class GraphClientExtensions
{
    public static IServiceCollection AddGraphClient(
        this IServiceCollection services, string? baseUrl, List<string>? scopes)
    {
        if (string.IsNullOrEmpty(baseUrl) || scopes?.Count == 0)
        {
            return services;
        }

        services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(
            options =>
            {
                scopes?.ForEach((scope) =>
                {
                    options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
                });
            });

        services.AddScoped<IAuthenticationProvider, GraphAuthenticationProvider>();

        services.AddScoped<IHttpProvider, HttpClientHttpProvider>(sp =>
            new HttpClientHttpProvider(new HttpClient()));

        services.AddScoped(sp =>
        {
            return new GraphServiceClient(
                baseUrl,
                sp.GetRequiredService<IAuthenticationProvider>(),
                sp.GetRequiredService<IHttpProvider>());
        });

        return services;
    }

    private class GraphAuthenticationProvider(IAccessTokenProvider tokenProvider, 
        IConfiguration config) : IAuthenticationProvider
    {
        private readonly IConfiguration config = config;

        public IAccessTokenProvider TokenProvider { get; } = tokenProvider;

        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            var result = await TokenProvider.RequestAccessToken(
                new AccessTokenRequestOptions()
                { 
                    Scopes = config.GetSection("MicrosoftGraph:Scopes").Get<string[]>()
                });

            if (result.TryGetToken(out var token))
            {
                request.Headers.Authorization ??= new AuthenticationHeaderValue(
                    "Bearer", token.Value);
            }
        }
    }

    private class HttpClientHttpProvider(HttpClient client) : IHttpProvider
    {
        private readonly HttpClient client = client;

        public ISerializer Serializer { get; } = new Serializer();

        public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);

        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
        {
            return client.SendAsync(request);
        }

        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
            HttpCompletionOption completionOption,
            CancellationToken cancellationToken)
        {
            return client.SendAsync(request, completionOption, cancellationToken);
        }

        public void Dispose()
        {
        }
    }
}

Important

앞의 코드가 AdditionalScopesToConsentDefaultAccessTokenScopes 범위를 AdditionalScopesToConsent추가하는 데 사용하는 DefaultAccessTokenScopes 이유에 대한 설명은 대 섹션을 참조하세요.

Program 파일에서 확장 메서드를 사용하여 Graph 클라이언트 서비스 및 구성을 AddGraphClient 추가합니다.

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

Graph SDK를 사용하는 구성 요소에서 Graph API 호출

다음 UserData 구성 요소는 삽입을 GraphServiceClient 사용하여 사용자의 ME-ID 프로필 데이터를 가져오고 휴대폰 번호를 표시합니다. ME-ID로 만든 테스트 사용자의 경우 Azure Portal에서 사용자의 ME-ID 프로필에 휴대폰 번호를 제공해야 합니다.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
    <p>Mobile Phone: @user.MobilePhone</p>
}

@code {
    private Microsoft.Graph.User? user;

    protected override async Task OnInitializedAsync()
    {
        var request = Client.Me.Request();
        user = await request.GetAsync();
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

앱에 사용자를 추가하려면 앱 역할 유의 여부에 관계없이 앱 등록에 사용자 할당 섹션을 참조하세요.

Graph SDK를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

Graph SDK를 사용하여 사용자 클레임 사용자 지정

다음 예제에서 앱은 ME-ID 사용자 프로필의 데이터에서 사용자의 휴대폰 번호 및 사무실 위치 클레임을 만듭니다. 앱에는 ME-ID로 User.Read 구성된 Graph API 범위가 있어야 합니다. 이 시나리오에 대한 모든 테스트 사용자는 AZURE Portal을 통해 추가할 수 있는 ME-ID 프로필에 휴대폰 번호와 사무실 위치가 있어야 합니다.

다음 사용자 지정 사용자 계정 팩터리에서 다음이 적용됩니다.

  • CreateUserAsync 메서드에서 정보 또는 오류를 기록하려는 경우 편의를 위해 ILogger(logger)가 포함됩니다.
  • throw되는 AccessTokenNotAvailableException 경우 사용자는 공급자로 identity 리디렉션되어 계정에 로그인합니다. 액세스 토큰 요청이 실패할 때 추가 또는 다른 작업을 수행할 수 있습니다. 예를 들어 앱은 AccessTokenNotAvailableException을 로깅하고 추가 조사를 위해 지원 티켓을 만들 수 있습니다.
  • 프레임워크의 RemoteUserAccount은 사용자의 계정을 나타냅니다. 앱에 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스가 필요한 경우, 다음 코드에서 RemoteUserAccount에 대한 사용자 지정 사용자 계정 클래스를 바꿉니다.

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor, 
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger)
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null && 
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null)
            {
                try
                {
                    var client = ActivatorUtilities
                        .CreateInstance<GraphServiceClient>(serviceProvider);
                    var request = client.Me.Request();
                    var user = await request.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

사용자 지정 사용자 계정 팩터리를 사용하도록 MSAL 인증을 구성합니다.

Program 파일이 Microsoft.AspNetCore.Components.WebAssembly.Authentication 네임스페이스를 사용하는지 확인합니다.

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

이 섹션의 예제는 파일의 섹션 wwwroot/appsettings.json 을 통해 앱 구성의 버전 및 범위를 사용하여 기본 URL을 읽는 MicrosoftGraph 방법을 기반으로 합니다. 이 문서의 앞부분에 있는 지침에 Program 따라 파일에 다음 줄이 이미 있어야 합니다.

var baseUrl = string.Join("/",
    builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
        "https://graph.microsoft.com",
    builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
        "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

파일에서 Program 확장 메서드에 대한 호출을 AddMsalAuthentication 찾습니다. CustomAccountFactory를 사용하여 계정 클레임 보안 주체 팩터리를 추가하는 에 대한 AddAccountClaimsPrincipalFactory 호출을 포함하는 코드를 다음으로 업데이트합니다.

앱에서 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스를 사용하는 경우 다음 코드에서 RemoteUserAccount에 대한 사용자 지정 사용자 계정 클래스를 바꿉니다.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount,
        CustomAccountFactory>();

다음 UserClaims 구성 요소를 사용하여 사용자가 ME-ID로 인증한 후 사용자의 클레임을 연구할 수 있습니다.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Graph SDK를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

다음 예제에서는 Graph API 호출에 알려진 HttpClient을 사용하여 사용자의 휴대폰 번호를 가져와 통화를 처리하거나 휴대폰 번호 클레임 및 사무실 위치 클레임을 포함하도록 사용자의 클레임을 사용자 지정합니다.

이 예제에서는 독립 실행형 Blazor WebAssembly 앱에 대한 Microsoft.Extensions.Http 패키지 참조가 필요합니다.

이 예제에서는 독립 실행형 Blazor WebAssembly 앱 또는 호스트된 Blazor WebAssembly 솔루션의 Client 앱을 위한 Microsoft.Extensions.Http에 대한 패키지 참조가 필요합니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Azure Portal에서 앱이 사용자를 대신하여 액세스할 수 있어야 하는 Microsoft Graph 데이터에 대해 위임된 권한(범위)† 부여합니다. 이 문서의 예제에서 앱 등록에는 사용자 데이터를 읽을 수 있는 위임된 권한(Microsoft.Graph>User.ReadAPI 권한범위, 형식: 위임됨)이 포함되어야 합니다. 이 User.Read 범위를 통해 사용자는 앱에 로그인할 수 있으며 앱에서 로그인한 사용자의 프로필 및 회사 정보를 읽을 수 있습니다. 자세한 내용은 Microsoft 플랫폼의 사용 권한 및 동의 개요 및 Microsoft identity Graph 권한 개요를 참조하세요.

†사용범위는 동일한 것을 의미하며 보안 설명서 및 Azure Portal에서 서로 바꿔서 사용됩니다. 텍스트가 Azure Portal을 참조하지 않는 한 이 문서에서는 Graph 권한을 참조할 때 범위/범위를 사용합니다.

범위는 대/소문자를 구분하지 않으므로 User.Read user.read. 두 형식 중 하나를 자유롭게 사용할 수 있지만 애플리케이션 코드에서 일관된 선택을 하는 것이 좋습니다.

Azure Portal에서 앱 등록에 Microsoft Graph API 범위를 추가한 후 앱의 파일에 다음 앱 설정 구성을 wwwroot/appsettings.json 추가합니다. 여기에는 Microsoft Graph 버전 및 범위가 포함된 그래프 기본 URL이 포함됩니다. 다음 예제에서는 이 문서의 이후 섹션에 나오는 예제에 대해 User.Read 범위를 지정합니다. 범위는 대/소문자를 구분하지 않습니다.

"MicrosoftGraph": {
  "BaseUrl": "https://graph.microsoft.com",
  "Version": "{VERSION}",
  "Scopes": [
    "user.read"
  ]
}

앞의 예제 {VERSION} 에서 자리 표시자는 Microsoft Graph API의 버전입니다(예: v1.0).

다음은 ME-ID를 공급자로 identity 사용하는 앱에 대한 전체 wwwroot/appsettings.json 구성 파일의 예입니다. 여기서 Microsoft Graph에 대해 사용자 데이터(user.read범위)를 읽습니다.

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "user.read"
    ]
  }
}

앞의 예제 {TENANT ID} 에서 자리 표시자는 디렉터리(테넌트) ID이고 {CLIENT ID} 자리 표시자는 애플리케이션(클라이언트) ID입니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

Graph API 작업을 위해 파일에 다음 GraphAuthorizationMessageHandler 클래스 및 프로젝트 구성 Program 을 만듭니다. 기본 URL 및 범위는 구성에서 처리기에 제공됩니다.

GraphAuthorizationMessageHandler.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace BlazorSample;

public class GraphAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public GraphAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigation, IConfiguration config)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: [ 
                string.Join("/",
                    config.GetSection("MicrosoftGraph")["BaseUrl"] ??
                        "https://graph.microsoft.com",
                    config.GetSection("MicrosoftGraph")["Version"] ??
                        "v1.0")
            ],
            scopes: config.GetSection("MicrosoftGraph:Scopes")
                        .Get<List<string>>() ?? [ "user.read" ]);
    }
}

권한 있는 URL의 후행 슬래시(/)가 필요합니다. 위의 코드는 앱 설정 구성에서 다음과 같은 권한 있는 URL을 빌드하거나 앱 설정 구성이 누락 https://graph.microsoft.com/v1.0/된 경우 기본적으로 다음 권한 있는 URL로 설정합니다.

파일에서 Program Graph API에 대해 명명된 이름을 HttpClient 구성합니다.

builder.Services.AddTransient<GraphAuthorizationMessageHandler>();

builder.Services.AddHttpClient("GraphAPI",
        client => client.BaseAddress = new Uri(
            string.Join("/",
                builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
                    "https://graph.microsoft.com",
                builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
                    "v1.0",
                string.Empty)))
    .AddHttpMessageHandler<GraphAuthorizationMessageHandler>();

앞의 예제에서는 임시 GraphAuthorizationMessageHandler DelegatingHandler 서비스 AddHttpMessageHandler로 등록됩니다. 임시 등록은 자체 DI 범위를 관리하는 IHttpClientFactory에 대해 권장됩니다. 자세한 내용은 다음 리소스를 참조하세요.

기본 주소의 후행 슬래시(/)가 필요합니다. 앞의 코드에서 세 번째 인수 string.Joinstring.Empty 후행 슬래시가 있는지 https://graph.microsoft.com/v1.0/확인하는 것입니다.

알려진 HttpClient을 사용하는 구성 요소에서 Graph API 호출

클래스는 UserInfo.cs ME-ID에서 사용되는 JSON 이름과 특성을 사용하여 JsonPropertyNameAttribute 필요한 사용자 프로필 속성을 지정합니다. 다음 예제에서는 사용자의 휴대폰 번호 및 사무실 위치에 대한 속성을 설정합니다.

UserInfo.cs:

using System.Text.Json.Serialization;

namespace BlazorSample;

public class UserInfo
{
    [JsonPropertyName("mobilePhone")]
    public string? MobilePhone { get; set; }

    [JsonPropertyName("officeLocation")]
    public string? OfficeLocation { get; set; }
}

다음 UserData 구성 요소에서는 Graph API 사용자의 프로필 데이터에 대한 요청을 발급하기 위해 HttpClient을 만듭니다. 리소스(me)는 me Graph API 요청에 대한 버전을 사용하여 기본 URL에 추가됩니다. Graph에서 반환된 JSON 데이터는 클래스 속성으로 UserInfo 역직렬화됩니다. 다음 예제에서는 휴대폰 번호를 가져옵니다. 원하는userInfo.OfficeLocation 경우 유사한 코드를 추가하여 사용자의 ME-ID 프로필 사무실 위치를 포함할 수 있습니다. 액세스 토큰 요청이 실패하면 사용자는 새 액세스 토큰을 위해 앱에 로그인하도록 리디렉션됩니다.

UserData.razor:

@page "/user-data"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@attribute [Authorize]
@inject IConfiguration Config
@inject IHttpClientFactory ClientFactory

<PageTitle>User Data</PageTitle>

<h1>Microsoft Graph User Data</h1>

@if (!string.IsNullOrEmpty(userInfo?.MobilePhone))
{
    <p>Mobile Phone: @userInfo.MobilePhone</p>
}

@code {
    private UserInfo? userInfo;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("GraphAPI");

            userInfo = await client.GetFromJsonAsync<UserInfo>("me");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-data">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Data
    </NavLink>
</div>

앱에 사용자를 추가하려면 앱 역할 유의 여부에 관계없이 앱 등록에 사용자 할당 섹션을 참조하세요.

다음 시퀀스는 Graph API 범위에 대한 새 사용자 흐름을 설명합니다.

  1. 새 사용자가 처음으로 앱에 로그인합니다.
  2. 사용자는 Azure 동의 UI에서 앱을 사용하는 데 동의합니다.
  3. 사용자가 처음으로 Graph API 데이터를 요청하는 구성 요소 페이지에 액세스합니다.
  4. 사용자는 Graph API 범위에 동의하도록 Azure 동의 UI로 리디렉션됩니다.
  5. Graph API 사용자 데이터가 반환됩니다.

초기 로그인 시 범위 프로비전(Graph API 범위에 대한 동의)이 수행되도록 하려면 MSAL 인증에 범위를 파일의 기본 액세스 토큰 범위 Program 로 제공합니다.

+ var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
+     .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);

+   foreach (var scope in scopes)
+   {
+       options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
+   }
});

Important

앞의 코드가 AdditionalScopesToConsentDefaultAccessTokenScopes 범위를 AdditionalScopesToConsent추가하는 데 사용하는 DefaultAccessTokenScopes 이유에 대한 설명은 대 섹션을 참조하세요.

앱에 대한 이전 변경 내용이 적용되면 사용자 흐름은 다음 시퀀스를 채택합니다.

  1. 새 사용자가 처음으로 앱에 로그인합니다.
  2. 사용자는 Azure 동의 UI에서 앱 및 Graph API 범위를 사용하는 데 동의합니다.
  3. 사용자가 처음으로 Graph API 데이터를 요청하는 구성 요소 페이지에 액세스합니다.
  4. Graph API 사용자 데이터가 반환됩니다.

Graph API를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

알려진 HttpClient를 사용하여 사용자 클레임 사용자 지정

다음 예제에서 앱은 ME-ID 사용자 프로필의 데이터에서 사용자의 휴대폰 번호 및 사무실 위치 클레임을 만듭니다. 앱에는 ME-ID로 User.Read 구성된 Graph API 범위가 있어야 합니다. ME-ID의 테스트 사용자 계정에는 Azure Portal을 통해 사용자 프로필에 추가할 수 있는 휴대폰 번호 및 사무실 위치에 대한 항목이 필요합니다.

이 문서의 앞부분에 있는 지침에 따라 아직 클래스를 앱에 추가 UserInfo 하지 않은 경우 다음 클래스를 추가하고 ME-ID에서 사용하는 특성 및 JSON 이름을 사용하여 필요한 사용자 프로필 속성을 JsonPropertyNameAttribute 지정합니다. 다음 예제에서는 사용자의 휴대폰 번호 및 사무실 위치에 대한 속성을 설정합니다.

UserInfo.cs:

using System.Text.Json.Serialization;

namespace BlazorSample;

public class UserInfo
{
    [JsonPropertyName("mobilePhone")]
    public string? MobilePhone { get; set; }

    [JsonPropertyName("officeLocation")]
    public string? OfficeLocation { get; set; }
}

다음 사용자 지정 사용자 계정 팩터리에서 다음이 적용됩니다.

  • CreateUserAsync 메서드에서 정보 또는 오류를 기록하려는 경우 편의를 위해 ILogger(logger)가 포함됩니다.
  • throw되는 AccessTokenNotAvailableException 경우 사용자는 공급자로 identity 리디렉션되어 계정에 로그인합니다. 액세스 토큰 요청이 실패할 때 추가 또는 다른 작업을 수행할 수 있습니다. 예를 들어 앱은 AccessTokenNotAvailableException을 로깅하고 추가 조사를 위해 지원 티켓을 만들 수 있습니다.
  • 프레임워크의 RemoteUserAccount은 사용자의 계정을 나타냅니다. 앱에 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스가 필요한 경우 다음 코드에서 RemoteUserAccount에 대한 사용자 지정 사용자 계정 클래스를 바꿉니다.

CustomAccountFactory.cs:

using System.Net.Http.Json;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IHttpClientFactory clientFactory,
        ILogger<CustomAccountFactory> logger)
    : AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IHttpClientFactory clientFactory = clientFactory;

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null && 
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null)
            {
                try
                {
                    var client = clientFactory.CreateClient("GraphAPI");

                    var userInfo = await client.GetFromJsonAsync<UserInfo>("me");

                    if (userInfo is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            userInfo.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            userInfo.OfficeLocation ?? "Not set"));
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

MSAL 인증은 사용자 지정 사용자 계정 팩터리를 사용하도록 구성됩니다. 먼저 Program 파일이 Microsoft.AspNetCore.Components.WebAssembly.Authentication 네임스페이스를 사용하는지 확인합니다.

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

파일에서 Program 확장 메서드에 대한 호출을 AddMsalAuthentication 찾습니다. CustomAccountFactory를 사용하여 계정 클레임 보안 주체 팩터리를 추가하는 에 대한 AddAccountClaimsPrincipalFactory 호출을 포함하는 코드를 다음으로 업데이트합니다.

앱에서 RemoteUserAccount를 확장하는 사용자 지정 사용자 계정 클래스를 사용하는 경우, 다음 코드에서 RemoteUserAccount에 대한 앱의 사용자 지정 사용자 계정 클래스를 바꿉니다.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
    RemoteUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd", 
            options.ProviderOptions.Authentication);
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, 
        CustomAccountFactory>();

앞의 예제는 MSAL에서 ME-ID 인증을 사용하는 앱에 대한 것입니다. OIDC 및 API 인증에 유사한 패턴이 있습니다. 자세한 내용은 ASP.NET Core Blazor WebAssembly 추가 보안 시나리오 문서의 페이로드 클레임으로 사용자 지정 섹션의 예제를 참조하세요.

다음 UserClaims 구성 요소를 사용하여 사용자가 ME-ID로 인증한 후 사용자의 클레임을 연구할 수 있습니다.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}
else
{
    <p>No claims found.</p>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider
            .GetAuthenticationStateAsync();
        var user = authState.User;

        claims = user.Claims;
    }
}

구성 요소의 구성 요소 페이지에 NavMenu 대한 링크를 추가합니다(Layout/NavMenu.razor).

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user-claims">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User Claims
    </NavLink>
</div>

Graph API를 로컬로 테스트할 때 느린 쿠키가 테스트를 방해하지 않도록 각 테스트에 대해 새 InPrivate/incognito 브라우저 세션을 사용하는 것이 좋습니다. 자세한 내용은 Microsoft Entra ID를 사용하여 ASP.NET Core Blazor WebAssembly 독립 실행형 앱 보안을 참조하세요.

앱 역할이 있거나 없는 앱 등록에 사용자 할당

Azure Portal에서 다음 단계를 수행하여 앱 등록에 사용자를 추가하고 사용자에게 역할을 할당할 수 있습니다.

사용자를 추가하려면 Azure Portal의 ME-ID 영역에서 사용자를 선택합니다.

  1. 새 사용자>새 사용자 만들기를 선택합니다.
  2. 사용자 만들기 템플릿을 사용합니다.
  3. 해당 영역에서 사용자의 정보를 Identity 제공합니다.
  4. 초기 암호를 생성하거나 사용자가 처음으로 로그인할 때 변경되는 초기 암호를 할당할 수 있습니다. 포털에서 생성된 암호를 사용하는 경우 지금 기록해 둡니다.
  5. 만들기를 선택하여 사용자를 만듭니다. 새 사용자 인터페이스 만들기가 닫히면 새로 고침을 선택하여 사용자 목록을 업데이트하고 새 사용자를 표시합니다.
  6. 이 문서의 예제에서는 사용자 목록에서 이름을 선택하고, 속성을 선택하고, 연락처 정보를 편집하여 휴대폰 번호를 제공하여 새 사용자에게 휴대폰 번호를 할당합니다.

앱 역할 없이 앱 에 사용자를 할당하려면 다음을 수행합니다.

  1. Azure Portal의 ME-ID 영역에서 엔터프라이즈 애플리케이션을 엽니다.
  2. 목록에서 앱을 선택합니다.
  3. 사용자 및 그룹을 선택합니다.
  4. 사용자/그룹 추가를 선택합니다.
  5. 사용자를 선택합니다.
  6. 할당 버튼을 선택합니다.

앱 역할을 사용하여 앱 에 사용자를 할당하려면 다음을 수행합니다.

  1. Microsoft Entra ID 그룹 및 역할을 사용하여 ASP.NET Core Blazor WebAssembly 의 지침에 따라 Azure Portal에서 앱 등록에 역할을 추가합니다.
  2. Azure Portal의 ME-ID 영역에서 엔터프라이즈 애플리케이션을 엽니다.
  3. 목록에서 앱을 선택합니다.
  4. 사용자 및 그룹을 선택합니다.
  5. 사용자/그룹 추가를 선택합니다.
  6. 사용자를 선택하고 앱에 액세스하기 위한 역할을 선택합니다. 사용자에 대한 모든 역할이 할당될 때까지 앱에 사용자를 추가하는 프로세스를 반복하여 여러 역할이 사용자에게 할당됩니다. 여러 역할이 있는 사용자는 앱의 사용자 및 그룹 목록에서 할당된 각 역할에 대해 한 번 나열됩니다.
  7. 할당 버튼을 선택합니다.

DefaultAccessTokenScopesAdditionalScopesToConsent

이 문서의 예제는 Graph API 범위를 프로비전하지 않고 AdditionalScopesToConsent프로DefaultAccessTokenScopes비전합니다.

AdditionalScopesToConsent 는 Azure 동의 UI를 통해 MSAL을 사용하여 처음으로 앱에 로그인할 때 사용자의 Graph API 범위를 프로비전할 수 없기 때문에 사용되지 않습니다. 사용자가 Graph SDK를 사용하여 처음으로 Graph API에 액세스하려고 하면 예외가 발생합니다.

Microsoft.Graph.Models.ODataErrors.ODataError: Access token is empty.

사용자가 제공된 Graph API 범위를 프로비전한 DefaultAccessTokenScopes후 앱은 후속 사용자 로그인에 사용할 AdditionalScopesToConsent 수 있습니다. 그러나 위임된 Graph 범위를 사용하여 새 사용자를 정기적으로 추가하거나 위임된 새 Graph API 범위를 앱에 추가해야 하는 프로덕션 앱에는 앱 코드를 변경하는 것은 의미가 없습니다.

사용자가 앱에 처음 로그인할 때 Graph API 액세스에 대한 범위를 프로비전하는 방법에 대한 앞의 설명은 다음에만 적용됩니다.

  • Graph SDK를 채택하는 앱.
  • 사용자에게 앱에 처음 로그인할 때 Graph 범위에 동의하도록 요청하는 명명된 HttpClient Graph API 액세스를 사용하는 앱입니다.

사용자가 첫 번째 로그인 시 Graph 범위에 동의하도록 요청하지 않는 명명된 이름을 HttpClient 사용하는 경우 사용자는 미리 구성된 이름을 HttpClient통해 DelegatingHandler Graph API에 대한 액세스를 처음 요청할 때 Graph API 범위 동의에 대한 Azure 동의 UI로 리디렉션됩니다. 그래프 범위가 처음에 명명 HttpClient 된 접근 방식에 동의하지 않는 경우 앱에서 호출하지 DefaultAccessTokenScopes 도 않습니다 AdditionalScopesToConsent . 자세한 내용은 이 문서의 명명된 HttpClient 적용 범위를 참조하세요.

호스트된 Blazor WebAssembly 솔루션

이 문서의 예제는 독립 실행형 Blazor WebAssembly 앱에서 직접 또는 호스트된 Blazor WebAssembly솔루션Client 앱에서 Graph API와 함께 Graph SDK 또는 알려진 HttpClient을 사용하는 경우와 관련이 있습니다. 이 문서에서 다루지 않는 추가 시나리오는 Client 호스트된 솔루션의 앱이 Web API를 통해 솔루션의 앱을 호출 Server 한 다음 Server , 앱이 Graph SDK/API를 사용하여 Microsoft Graph를 호출하고 데이터를 앱에 반환하는 것입니다 Client . 이는 지원되는 접근 방식이지만 이 문서에서는 다루지 않습니다. 이 방법을 채택하려는 경우 다음을 수행합니다.

  • Client 앱에서 Server 앱에 대한 요청을 실행하고 데이터를 Client 앱으로 반환하는 웹 API 측면에 대한 ASP.NET Core Blazor 앱에서 웹 API 호출의 지침을 따릅니다.
  • 기본 Microsoft Graph 설명서의 지침에 따라, 이 시나리오에서 솔루션의 Server 앱인 일반적인 ASP.NET Core 앱에서 Graph SDK를 사용합니다. 프로젝트 템플릿을 Blazor WebAssembly 사용하여 조직 권한 부여(단일 조직/또는 여러 조직/SingleOrgMultiOrg)와 Microsoft Graph 옵션(Microsoft 플랫폼>연결된 서비스>Visual Studio에서 Microsoft identity Graph 권한 추가 또는 --calls-graph .NET CLI dotnet new 명령을 사용하는 옵션)을 사용하여 호스트 Blazor WebAssembly 된 솔루션(ASP.NET Core Hosted/-h|--hosted)Server을 만드는 경우 솔루션의 앱은 프로젝트 템플릿에서 솔루션을 만들 때 Graph SDK를 사용하도록 구성됩니다.

추가 리소스

일반 지침

  • Microsoft Graph 설명서
  • Microsoft Graph 샘플 Blazor WebAssembly 앱: 이 샘플에서는 Microsoft Graph .NET SDK를 사용하여 Blazor WebAssembly 앱에서 Office 365 데이터에 액세스하는 방법을 보여줍니다.
  • Microsoft Graph 자습서Microsoft Graph 샘플 ASP.NET Core 앱을 사용하여 .NET 앱 빌드: 이러한 리소스는 Server 앱이 Client 앱을 대신하여 일반적인 ASP.NET Core 앱으로 Microsoft Graph에 액세스하도록 구성된 경우의 호스트된Blazor WebAssembly 솔루션에 가장 적합합니다. Client앱은 웹 API를 사용하여 Graph 데이터에 대한 Server 앱을 요청합니다. 이러한 리소스는 클라이언트 쪽Blazor WebAssembly 앱에서 Graph를 호출하는 데 직접 적용되지는 않지만 연결된 리소스의 ME-ID 앱 구성 및 Microsoft Graph 코딩 방법은 독립 실행형 Blazor WebAssembly 앱과 관련이 있으며 일반적인 모범 사례를 참조해야 합니다.

보안 지침