자습서: 보안 .NET 앱에서 Microsoft Graph에 사용자로 액세스

Azure App Service에서 실행되는 웹앱에서 Microsoft Graph에 액세스하는 방법에 대해 알아봅니다.

Microsoft Graph 액세스를 보여주는 다이어그램.

웹앱에서 Microsoft Graph에 대한 액세스 권한을 추가하고 로그인한 사용자로 작업을 수행하려고 합니다. 이 섹션에서는 위임된 권한을 웹앱에 부여하고 Azure AD(Azure Active Directory)에서 로그인한 사용자의 프로필 정보를 가져오는 방법을 설명합니다.

이 자습서에서는 다음과 같은 작업을 수행하는 방법을 살펴봅니다.

  • 위임된 권한을 웹앱에 부여합니다.
  • 로그인한 사용자를 위해 웹앱에서 Microsoft Graph를 호출합니다.

Azure를 구독하고 있지 않다면 시작하기 전에 Azure 체험 계정을 만듭니다.

필수 구성 요소

프런트 엔드 액세스 권한을 부여하여 Microsoft Graph 호출

웹앱에서 인증 및 권한 부여를 사용하도록 설정했으므로, 웹앱이 Microsoft ID 플랫폼에 등록되고 Azure AD 애플리케이션에서 지원됩니다. 이 단계에서는 사용자를 위해 Microsoft Graph에 액세스할 수 있는 권한을 웹앱에 부여합니다. (기술적으로는, 웹앱의 Azure AD 애플리케이션에 사용자를 위해 Microsoft Graph Azure AD 애플리케이션에 액세스할 수 있는 권한을 부여합니다.)

  1. Azure Portal 메뉴에서 Azure Active Directory를 선택하거나 아무 페이지에서 Azure Active Directory를 검색한 후 선택합니다.

  2. 앱 등록>소유 애플리케이션>이 디렉터리의 모든 애플리케이션 보기를 선택합니다. 웹앱 이름을 선택한 다음, API 사용 권한을 선택합니다.

  3. 사용 권한 추가를 선택한 다음, Microsoft API 및 Microsoft Graph를 선택합니다.

  4. 위임된 권한을 선택한 다음, 목록에서 User.Read를 선택합니다. 권한 추가를 선택합니다.

사용 가능한 액세스 토큰을 반환하도록 App Service 구성

이제 웹앱에 로그인한 사용자로 Microsoft Graph에 액세스하는 데 필요한 권한이 있습니다. 이 단계에서는 Microsoft Graph에 액세스하는 데 사용할 수 있는 액세스 토큰을 제공하도록 App Service 인증 및 권한 부여를 구성합니다. 이 단계에서는 다운스트림 서비스(Microsoft Graph)에 대한 User.Read 범위를 추가해야 합니다. https://graph.microsoft.com/User.Read.

중요

사용 가능한 액세스 토큰을 반환하도록 App Service를 구성하지 않으면 코드에서 Microsoft Graph API를 호출할 때 CompactToken parsing failed with error code: 80049217 오류가 발생합니다.

Azure Resource Explorer로 이동하고 리소스 트리를 사용하여 웹앱을 찾습니다. 리소스 URL은 https://resources.azure.com/subscriptions/subscriptionId/resourceGroups/SecureWebApp/providers/Microsoft.Web/sites/SecureWebApp20200915115914와 유사해야 합니다.

이제 Azure Resource Explorer가 리소스 트리에서 웹앱이 선택된 상태로 열립니다.

  1. 페이지의 위쪽에서 읽기/쓰기 를 선택하여 Azure 리소스 편집이 가능하도록 설정합니다.

  2. 왼쪽 브라우저에서 구성>authsettingsV2로 드릴다운합니다.

  3. authsettingsV2 보기에서 편집을 선택합니다.

  4. identityProviders ->azureActiveDirectorylogin 섹션을 찾아 "loginParameters":[ "response_type=code id_token","scope=openid offline_access profile https://graph.microsoft.com/User.Read" ]loginParameters 설정을 추가합니다.

    "identityProviders": {
        "azureActiveDirectory": {
          "enabled": true,
          "login": {
            "loginParameters":[
              "response_type=code id_token",
              "scope=openid offline_access profile https://graph.microsoft.com/User.Read"
            ]
          }
        }
      }
    },
    
  5. PUT을 선택하여 설정을 저장합니다. 이 설정이 적용되려면 몇 분 정도 걸릴 수 있습니다. 이제 웹앱이 적절한 액세스 토큰을 사용하여 Microsoft Graph에 액세스하도록 구성되었습니다. 그렇지 않으면 Microsoft Graph에서 압축 토큰의 형식이 올바르지 않다는 오류를 반환합니다.

.NET으로 Microsoft Graph 호출

이제 웹앱에 필요한 권한이 있으며 Microsoft Graph의 클라이언트 ID도 로그인 매개 변수에 추가됩니다.

Microsoft.Identity.Web 라이브러리를 사용하여 웹앱은 Microsoft Graph 인증을 위한 액세스 토큰을 얻습니다. 버전 1.2.0 이상에서 Microsoft.Identity.Web 라이브러리는 App Service 인증/권한 부여 모듈과 통합되며 함께 실행할 수 있습니다. Microsoft.Identity.Web은 웹앱이 App Service에서 호스트되는 것을 감지하고 App Service 인증/권한 부여 모듈에서 액세스 토큰을 가져옵니다. 그런 다음, 액세스 토큰은 Microsoft Graph API를 사용하여 인증된 요청에 전달됩니다.

이 코드를 샘플 애플리케이션의 일부로 보려면 다음을 참조하세요.

참고

Microsoft.Identity.Web 라이브러리는 기본 인증/권한 부여를 위해 또는 Microsoft Graph를 사용하여 요청을 인증하기 위해 웹앱에 필요하지 않습니다. App Service 인증/권한 부여 모듈만 사용하도록 설정된 상태에서 다운스트림 API를 안전하게 호출하는 것이 가능합니다.

하지만 App Service 인증/권한 부여는 보다 기본적인 인증 시나리오를 위해 설계되었습니다. 더 복잡한 시나리오(예: 사용자 지정 클레임 처리)의 경우 Microsoft.Identity.Web 라이브러리 또는 Microsoft 인증 라이브러리가 필요합니다. 처음에는 설정 및 구성 작업이 약간 더 많지만 Microsoft.Identity.Web 라이브러리는 App Service 인증/권한 부여 모듈과 함께 실행할 수 있습니다. 나중에 웹앱에서 더 복잡한 시나리오를 처리해야 하는 경우 App Service 인증/권한 부여 모듈을 사용하지 않도록 설정할 수 있으며 Microsoft.Identity.Web은 이미 앱의 일부입니다.

클라이언트 라이브러리 패키지 설치

.NET Core 명령줄 인터페이스 또는 Visual Studio의 패키지 관리자 콘솔을 사용하여 프로젝트에 Microsoft.Identity.WebMicrosoft.Identity.Web.MicrosoftGraph NuGet 패키지를 설치합니다.

.NET Core 명령줄

명령줄을 열고 프로젝트 파일이 포함된 디렉터리로 전환합니다.

설치 명령을 실행합니다.

dotnet add package Microsoft.Identity.Web.MicrosoftGraph

dotnet add package Microsoft.Identity.Web

패키지 관리자 콘솔

Visual Studio에서 프로젝트/솔루션을 열고, 도구>NuGet 패키지 관리자>패키지 관리자 콘솔 명령을 사용하여 콘솔을 엽니다.

설치 명령을 실행합니다.

Install-Package Microsoft.Identity.Web.MicrosoftGraph

Install-Package Microsoft.Identity.Web

Startup.cs

Startup.cs 파일에서 AddMicrosoftIdentityWebApp 메서드는 웹앱에 Microsoft.Identity.Web을 추가합니다. AddMicrosoftGraph 메서드는 Microsoft Graph 지원을 추가합니다.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

// Some code omitted for brevity.
public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
                    .EnableTokenAcquisitionToCallDownstreamApi()
                        .AddMicrosoftGraph(Configuration.GetSection("Graph"))
                        .AddInMemoryTokenCaches();

        services.AddRazorPages();
    }
}

appsettings.json

AzureAd는 Microsoft.Identity.Web 라이브러리에 대한 구성을 지정합니다. Azure Portal의 포털 메뉴에서 Azure Active Directory를 선택한 다음, 앱 등록을 선택합니다. App Service 인증/권한 부여 모듈을 활성화할 때 생성된 앱 등록을 선택합니다. (앱 등록은 웹앱과 이름이 같아야 합니다.) 테넌트 ID와 클라이언트 ID는 앱 등록 개요 페이지에서 찾을 수 있습니다. 도메인 이름은 테넌트의 Azure AD 개요 페이지에서 찾을 수 있습니다.

Graph는 앱에 필요한 Microsoft Graph 엔드포인트와 초기 범위를 지정합니다.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "fourthcoffeetest.onmicrosoft.com",
    "TenantId": "[tenant-id]",
    "ClientId": "[client-id]",
    // To call an API
    "ClientSecret": "[secret-from-portal]", // Not required by this scenario
    "CallbackPath": "/signin-oidc"
  },

  "Graph": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "user.read"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

사용자를 대신하여 Microsoft Graph 호출

다음 예제는 로그인한 사용자로 Microsoft Graph를 호출하고 일부 사용자 정보를 가져오는 방법을 보여줍니다. GraphServiceClient 개체가 컨트롤러에 삽입되고 인증이 Microsoft.Identity.Web 라이브러리에 의해 구성되었습니다.

// Index.cshtml.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Graph;
using System.IO;
using Microsoft.Identity.Web;
using Microsoft.Extensions.Logging;

// Some code omitted for brevity.

[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly GraphServiceClient _graphServiceClient;

    public IndexModel(ILogger<IndexModel> logger, GraphServiceClient graphServiceClient)
    {
        _logger = logger;
        _graphServiceClient = graphServiceClient;
    }

    public async Task OnGetAsync()
    {
        try
        {
            var user = await _graphServiceClient.Me.Request().GetAsync();
            ViewData["Me"] = user;
            ViewData["name"] = user.DisplayName;

            using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
            {
                byte[] photoByte = ((MemoryStream)photoStream).ToArray();
                ViewData["photo"] = Convert.ToBase64String(photoByte);
            }
        }
        catch (Exception ex)
        {
            ViewData["photo"] = null;
        }
    }
}

리소스 정리

여러 부분으로 구성된 이 자습서의 모든 단계를 완료한 경우 리소스 그룹에 앱 서비스, 앱 서비스 호스팅 계획 및 스토리지 계정을 만들었습니다. 또한 Azure Active Directory에서 앱 등록을 만들었습니다. 더 이상 필요하지 않은 경우 요금이 계속 청구되지 않도록 이러한 리소스와 앱 등록을 삭제합니다.

이 자습서에서는 다음과 같은 작업을 수행하는 방법을 살펴봅니다.

  • 자습서를 수행하는 동안 만든 Azure 리소스를 삭제합니다.

리소스 그룹 삭제

Azure Portal의 포털 메뉴에서 리소스 그룹을 선택하고 앱 서비스 및 앱 서비스 계획이 포함된 리소스 그룹을 선택합니다.

리소스 그룹 삭제를 선택하여 리소스 그룹 및 모든 리소스를 삭제합니다.

리소스 그룹을 삭제하는 방법을 보여주는 스크린샷.

이 명령을 실행하는 데 몇 분 정도 걸릴 수 있습니다.

앱 등록 삭제

포털 메뉴에서 Azure Active Directory>앱 등록을 선택합니다. 그런 다음, 사용자가 만든 애플리케이션을 선택합니다. 앱 등록 선택을 보여주는 스크린샷.

앱 등록 개요에서 삭제를 선택합니다. 앱 등록 삭제를 보여주는 스크린샷.

다음 단계

이 자습서에서는 다음 작업 방법을 알아보았습니다.

  • 위임된 권한을 웹앱에 부여합니다.
  • 로그인한 사용자를 위해 웹앱에서 Microsoft Graph를 호출합니다.