Microsoft.Identity.Web 및 Microsoft Graph SDK를 사용하여 ASP.NET Core 및 OWIN 애플리케이션에서 Microsoft Graph를 호출하여 Microsoft 365 데이터 및 서비스에 액세스합니다.
Microsoft Graph 통합 이해
Microsoft Graph Microsoft 365, Windows 및 Enterprise Mobility + Security 데이터에 액세스하기 위한 통합 API 엔드포인트를 제공합니다. Microsoft. Identity.Web은 Microsoft Graph 대한 인증 및 토큰 획득을 간소화하는 반면, Microsoft Graph SDK는 Graph 엔드포인트를 호출하기 위한 유창한 형식의 API를 제공합니다.
Microsoft.Identity.Web.GraphServiceClient를 선택합니다.
다음과 같은 이점 때문에 Microsoft Graph를 호출하는 데에는 Microsoft.Identity.Web.GraphServiceClient가 권장되는 방법입니다.
- 자동 토큰 획득: 사용자 및 앱 토큰을 원활하게 처리
- 토큰 캐싱: 성능을 위한 기본 제공 캐싱
- Fluent API: 형식 안전, IntelliSense 친화적인 그래프 호출
- 증분 동의: 요청 시 추가 범위 요청
- 여러 인증 체계: 웹앱 및 웹 API 지원
- v1.0 및 베타: 안정적인 엔드포인트와 미리 보기 엔드포인트를 함께 사용
필수 패키지 설치
Microsoft Graph SDK 통합 패키지를 설치합니다.
dotnet add package Microsoft.Identity.Web.GraphServiceClient
Microsoft Graph 베타 API의 경우:
dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta
ASP.NET Core 설정
1. 서비스 구성
애플리케이션에 Microsoft Graph 지원을 추가합니다.
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// Add authentication (web app or web API)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
// Add Microsoft Graph support
builder.Services.AddMicrosoftGraph();
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. appsettings.json 구성
구성 파일에서 그래프 옵션을 구성합니다.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"CallbackPath": "/signin-oidc"
},
"DownstreamApis": {
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": ["User.Read", "User.ReadBasic.All"]
}
}
}
코드를 사용하여 구성:
builder.Services.AddMicrosoftGraph(options =>
{
builder.Configuration.GetSection("DownstreamApis:MicrosoftGraph").Bind(options);
});
또는 코드에서 직접 구성합니다.
builder.Services.AddMicrosoftGraph();
builder.Services.Configure<MicrosoftGraphOptions>(options =>
{
options.BaseUrl = "https://graph.microsoft.com/v1.0";
options.Scopes = new[] { "User.Read", "Mail.Read" };
});
3. 국가별 클라우드 지원 구성
국가별 클라우드에서 Microsoft Graph 사용하려면 구성에서 BaseUrl을 지정합니다.
{
"DownstreamApis": {
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.us/v1.0",
"Scopes": ["User.Read"]
}
}
}
엔드포인트 URL은 Microsoft Graph 배포를 참조하세요.
GraphServiceClient 사용
GraphServiceClient 삽입
생성자에 GraphServiceClient를 삽입합니다.
using Microsoft.Graph;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize]
public class ProfileController : Controller
{
private readonly GraphServiceClient _graphClient;
public ProfileController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
public async Task<IActionResult> Index()
{
// Call Microsoft Graph
var user = await _graphClient.Me.GetAsync();
return View(user);
}
}
위임된 권한 사용(사용자 토큰)
위임된 권한이 있는 로그인한 사용자를 대신하여 Graph를 호출합니다.
기본 사용자 프로필 검색
Microsoft Graph 현재 사용자의 프로필 정보를 검색합니다.
[Authorize]
public class ProfileController : Controller
{
private readonly GraphServiceClient _graphClient;
public ProfileController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
public async Task<IActionResult> Me()
{
// Get current user's profile
var user = await _graphClient.Me.GetAsync();
return View(new UserViewModel
{
DisplayName = user.DisplayName,
Mail = user.Mail,
JobTitle = user.JobTitle
});
}
}
증분 동의 요청
애플리케이션에 필요한 경우 동적으로 추가 범위를 요청합니다.
[Authorize]
[AuthorizeForScopes("Mail.Read")]
public class MailController : Controller
{
private readonly GraphServiceClient _graphClient;
public MailController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
public async Task<IActionResult> Inbox()
{
try
{
// Request Mail.Read scope dynamically
var messages = await _graphClient.Me.Messages
.GetAsync(r => r.Options.WithScopes("Mail.Read"));
return View(messages);
}
catch (MicrosoftIdentityWebChallengeUserException)
{
// ASP.NET Core will redirect user to consent
// thansk to the AuthorizeForScopes attribute.
throw;
}
}
}
쿼리 옵션 적용
Graph SDK 쿼리 옵션을 사용하여 결과를 필터링, 선택 및 정렬합니다.
public async Task<IActionResult> UnreadMessages()
{
var messages = await _graphClient.Me.Messages
.GetAsync(requestConfiguration =>
{
requestConfiguration.QueryParameters.Filter = "isRead eq false";
requestConfiguration.QueryParameters.Select = new[] { "subject", "from", "receivedDateTime" };
requestConfiguration.QueryParameters.Orderby = new[] { "receivedDateTime desc" };
requestConfiguration.QueryParameters.Top = 10;
// Request specific scope
requestConfiguration.Options.WithScopes("Mail.Read");
});
return View(messages);
}
결과 페이지 넘기기
각 페이지를 반복하여 Microsoft Graph의 페이징 결과를 처리합니다.
public async Task<IActionResult> AllUsers()
{
var allUsers = new List<User>();
// Get first page
var users = await _graphClient.Users
.GetAsync(r => r.Options.WithScopes("User.ReadBasic.All"));
// Add first page
allUsers.AddRange(users.Value);
// Iterate through remaining pages
var pageIterator = PageIterator<User, UserCollectionResponse>
.CreatePageIterator(
_graphClient,
users,
user =>
{
allUsers.Add(user);
return true; // Continue iteration
});
await pageIterator.IterateAsync();
return View(allUsers);
}
애플리케이션 사용 권한(앱 전용 토큰)
사용자 컨텍스트가 필요하지 않은 경우 애플리케이션 권한으로 Graph를 호출합니다.
WithAppOnly를 사용하여 그래프 호출()
이 메서드를 WithAppOnly() 사용하여 애플리케이션 사용 권한으로 Graph를 호출합니다.
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
private readonly GraphServiceClient _graphClient;
public AdminController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
[HttpGet("users/count")]
public async Task<ActionResult<int>> GetUserCount()
{
// Get count using app permissions
var count = await _graphClient.Users.Count
.GetAsync(r => r.Options.WithAppOnly());
return Ok(count);
}
[HttpGet("applications")]
public async Task<ActionResult> GetApplications()
{
// List applications using app permissions
var apps = await _graphClient.Applications
.GetAsync(r => r.Options.WithAppOnly());
return Ok(apps.Value);
}
}
앱 권한 구성
appsettings.json앱 토큰 요청을 지정합니다.
{
"DownstreamApis": {
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"RequestAppToken": true
}
}
}
범위는 ["https://graph.microsoft.com/.default"]로 자동 설정됩니다.
자세한 앱 전용 옵션 구성
코드에서 명시적 앱 전용 인증 옵션을 설정합니다.
public async Task<IActionResult> GetApplicationsDetailed()
{
var apps = await _graphClient.Applications
.GetAsync(r =>
{
r.Options.WithAuthenticationOptions(options =>
{
// Request app token explicitly
options.RequestAppToken = true;
// Scopes automatically become [.default]
// No need to specify: options.Scopes = new[] { "https://graph.microsoft.com/.default" };
});
});
return Ok(apps);
}
여러 인증 체계 처리
앱에서 여러 인증 체계(예: 웹앱 + API)를 사용하는 경우 사용할 체계를 지정합니다.
using Microsoft.AspNetCore.Authentication.JwtBearer;
[Authorize]
public class ApiDataController : ControllerBase
{
private readonly GraphServiceClient _graphClient;
public ApiDataController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
[HttpGet("profile")]
public async Task<ActionResult> GetProfile()
{
// Specify JWT Bearer scheme
var user = await _graphClient.Me
.GetAsync(r => r.Options
.WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme));
return Ok(user);
}
}
자세한 구성표 옵션 설정
코드에서 인증 체계 및 범위를 명시적으로 설정합니다.
public async Task<ActionResult> GetMailWithScheme()
{
var messages = await _graphClient.Me.Messages
.GetAsync(r =>
{
r.Options.WithAuthenticationOptions(options =>
{
// Specify authentication scheme
options.AcquireTokenOptions.AuthenticationOptionsName =
JwtBearerDefaults.AuthenticationScheme;
// Specify scopes
options.Scopes = new[] { "Mail.Read" };
});
});
return Ok(messages);
}
v1.0 및 베타 엔드포인트 모두 사용
동일한 애플리케이션에서 Microsoft Graph v1.0 및 베타를 등록하고 호출합니다.
1. 두 패키지 모두 설치
dotnet add package Microsoft.Identity.Web.GraphServiceClient
dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta
2. 두 서비스 모두 등록
using Microsoft.Identity.Web;
builder.Services.AddMicrosoftGraph();
builder.Services.AddMicrosoftGraphBeta();
3. 두 클라이언트 사용
using GraphServiceClient = Microsoft.Graph.GraphServiceClient;
using GraphBetaServiceClient = Microsoft.Graph.Beta.GraphServiceClient;
public class MyController : Controller
{
private readonly GraphServiceClient _graphClient;
private readonly GraphBetaServiceClient _graphBetaClient;
public MyController(
GraphServiceClient graphClient,
GraphBetaServiceClient graphBetaClient)
{
_graphClient = graphClient;
_graphBetaClient = graphBetaClient;
}
public async Task<IActionResult> GetData()
{
// Use stable v1.0 endpoint
var user = await _graphClient.Me.GetAsync();
// Use beta endpoint for preview features
var profile = await _graphBetaClient.Me.Profile.GetAsync();
return View(new { user, profile });
}
}
일괄 처리 요청 보내기
여러 Graph 호출을 단일 HTTP 요청으로 결합하여 성능을 향상시킵니다.
using Microsoft.Graph.Models;
public async Task<IActionResult> GetDashboard()
{
var batchRequestContent = new BatchRequestContentCollection(_graphClient);
// Add multiple requests to batch
var userRequest = _graphClient.Me.ToGetRequestInformation();
var messagesRequest = _graphClient.Me.Messages.ToGetRequestInformation();
var eventsRequest = _graphClient.Me.Events.ToGetRequestInformation();
var userRequestId = await batchRequestContent.AddBatchRequestStepAsync(userRequest);
var messagesRequestId = await batchRequestContent.AddBatchRequestStepAsync(messagesRequest);
var eventsRequestId = await batchRequestContent.AddBatchRequestStepAsync(eventsRequest);
// Send batch request
var batchResponse = await _graphClient.Batch.PostAsync(batchRequestContent);
// Extract responses
var user = await batchResponse.GetResponseByIdAsync<User>(userRequestId);
var messages = await batchResponse.GetResponseByIdAsync<MessageCollectionResponse>(messagesRequestId);
var events = await batchResponse.GetResponseByIdAsync<EventCollectionResponse>(eventsRequestId);
return View(new DashboardViewModel
{
User = user,
Messages = messages.Value,
Events = events.Value
});
}
일반적인 그래프 패턴 적용
이러한 패턴을 사용하여 애플리케이션에서 자주 Microsoft Graph 작업을 수행합니다.
사용자의 관리자 가져오기
디렉터리에서 로그인한 사용자의 관리자를 검색합니다.
public async Task<IActionResult> GetManager()
{
var manager = await _graphClient.Me.Manager.GetAsync();
// Cast to User (manager is DirectoryObject)
if (manager is User managerUser)
{
return View(managerUser);
}
return NotFound("Manager not found");
}
사용자의 사진 가져오기
로그인한 사용자의 프로필 사진을 스트림으로 다운로드합니다.
public async Task<IActionResult> GetPhoto()
{
try
{
var photoStream = await _graphClient.Me.Photo.Content.GetAsync();
return File(photoStream, "image/jpeg");
}
catch (ServiceException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return NotFound("Photo not available");
}
}
전자 메일 보내기
로그인한 사용자를 대신하여 전자 메일 메시지를 보냅니다.
public async Task<IActionResult> SendEmail([FromBody] EmailRequest request)
{
var message = new Message
{
Subject = request.Subject,
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = request.Body
},
ToRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = request.ToEmail
}
}
}
};
await _graphClient.Me.SendMail
.PostAsync(new SendMailPostRequestBody
{
Message = message,
SaveToSentItems = true
},
requestConfiguration =>
{
requestConfiguration.Options.WithScopes("Mail.Send");
});
return Ok("Email sent");
}
일정 이벤트 만들기
로그인한 사용자에 대한 참석자가 포함된 새 일정 이벤트를 만듭니다.
public async Task<IActionResult> CreateEvent([FromBody] EventRequest request)
{
var newEvent = new Event
{
Subject = request.Subject,
Start = new DateTimeTimeZone
{
DateTime = request.StartTime.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "UTC"
},
End = new DateTimeTimeZone
{
DateTime = request.EndTime.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "UTC"
},
Attendees = request.Attendees.Select(email => new Attendee
{
EmailAddress = new EmailAddress { Address = email },
Type = AttendeeType.Required
}).ToList()
};
var createdEvent = await _graphClient.Me.Events
.PostAsync(newEvent, r => r.Options.WithScopes("Calendars.ReadWrite"));
return Ok(createdEvent);
}
사용자 검색
표시 이름 또는 전자 메일 주소를 사용하여 디렉터리에서 사용자를 검색합니다.
public async Task<IActionResult> SearchUsers(string searchTerm)
{
var users = await _graphClient.Users
.GetAsync(requestConfiguration =>
{
requestConfiguration.QueryParameters.Filter =
$"startswith(displayName,'{searchTerm}') or startswith(mail,'{searchTerm}')";
requestConfiguration.QueryParameters.Select =
new[] { "displayName", "mail", "jobTitle" };
requestConfiguration.QueryParameters.Top = 10;
requestConfiguration.Options.WithScopes("User.ReadBasic.All");
});
return Ok(users.Value);
}
OWIN 지원 구현
OWIN을 사용하는 ASP.NET 애플리케이션의 경우 토큰 획득자 팩터리를 구성하고 Microsoft Graph 서비스를 등록합니다.
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder app)
{
OwinTokenAcquirerFactory factory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();
app.AddMicrosoftIdentityWebApi(factory);
factory.Services
.AddMicrosoftGraph();
factory.Build();
}
}
2. 컨트롤러에서 API 호출
컨트롤러에서 GraphServiceClient 인스턴스를 검색하고 Microsoft Graph 호출합니다.
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using System.Web.Http;
[Authorize]
public class DataController : ApiController
{
public DataController()
{
}
public async Task<IHttpActionResult> GetMyProfile()
{
GraphServiceClient graphServiceClient = this.GetGraphServiceClient();
var me = await graphServiceClient.Me.GetAsync();
return Ok(me);
}
}
Microsoft.Identity.Web.MicrosoftGraph 2.x에서 마이그레이션하기
이전 Microsoft.Identity.Web.MicrosoftGraph 패키지(SDK 4.x)에서 마이그레이션하는 경우, 다음 주요 변경 사항을 검토하십시오.
1. 이전 패키지 제거 및 새로 추가
dotnet remove package Microsoft.Identity.Web.MicrosoftGraph
dotnet add package Microsoft.Identity.Web.GraphServiceClient
2. 메서드 호출 업데이트
메서드가 .Request() SDK 5.x에서 제거되었습니다.
이전(SDK 4.x):
var user = await _graphClient.Me.Request().GetAsync();
var messages = await _graphClient.Me.Messages
.Request()
.WithScopes("Mail.Read")
.GetAsync();
After(SDK 5.x):
var user = await _graphClient.Me.GetAsync();
var messages = await _graphClient.Me.Messages
.GetAsync(r => r.Options.WithScopes("Mail.Read"));
3. WithScopes() 위치 업데이트
Before:
var users = await _graphClient.Users
.Request()
.WithScopes("User.Read.All")
.GetAsync();
After:
var users = await _graphClient.Users
.GetAsync(r => r.Options.WithScopes("User.Read.All"));
4. WithAppOnly() 위치 업데이트
Before:
var apps = await _graphClient.Applications
.Request()
.WithAppOnly()
.GetAsync();
After:
var apps = await _graphClient.Applications
.GetAsync(r => r.Options.WithAppOnly());
5. WithAuthenticationScheme() 위치 업데이트
Before:
var user = await _graphClient.Me
.Request()
.WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme)
.GetAsync();
After:
var user = await _graphClient.Me
.GetAsync(r => r.Options
.WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme));
전체 마이그레이션 세부 정보는 Microsoft Graph .NET SDK v5 변경 로그 참조하세요.
오류를 처리하십시오.
ServiceException 처리
Graph API 오류를 정상적으로 처리하려면 ODataError 및 MicrosoftIdentityWebChallengeUserException catch합니다.
using Microsoft.Graph.Models.ODataErrors;
public async Task<IActionResult> GetData()
{
try
{
var user = await _graphClient.Me.GetAsync();
return Ok(user);
}
catch (ODataError ex) when (ex.ResponseStatusCode == 404)
{
return NotFound("Resource not found");
}
catch (ODataError ex) when (ex.ResponseStatusCode == 403)
{
return Forbid("Insufficient permissions");
}
catch (MicrosoftIdentityWebChallengeUserException)
{
// User needs to consent
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Graph API call failed");
return StatusCode(500, "An error occurred");
}
}
모범 사례 준수
1. 최소 범위 요청
필요한 범위만 요청하세요.
// Bad: Requesting too many scopes
options.Scopes = new[] { "User.Read", "Mail.ReadWrite", "Calendars.ReadWrite", "Files.ReadWrite.All" };
// Good: Request only what you need
options.Scopes = new[] { "User.Read" };
2. 증분 동의 사용
필요한 경우에만 추가 범위를 요청합니다.
// Sign-in: Only User.Read
// Later, when accessing mail:
var messages = await _graphClient.Me.Messages
.GetAsync(r => r.Options.WithScopes("Mail.Read"));
3. 캐시 GraphServiceClient
GraphServiceClient는 재사용해도 안전합니다. 싱글톤으로 등록하거나 DI에서 삽입합니다.
4. Select를 사용하여 응답 크기 줄이기
// Bad: Getting all properties
var users = await _graphClient.Users.GetAsync();
// Good: Select only needed properties
var users = await _graphClient.Users
.GetAsync(r => r.QueryParameters.Select =
new[] { "displayName", "mail", "id" });
일반적인 문제 해결
"작업을 완료할 수 있는 권한이 부족"을 해결합니다.
원인: 앱에 필요한 그래프 권한이 없습니다.
해결 방법:
- 앱 등록에 필요한 API 권한 추가
- 앱 권한에 필요한 관리자 동의
- 위임된 권한에 필요한 사용자 동의
"AADSTS65001: 사용자 또는 관리자가 동의하지 않았습니다." 해결
원인: 사용자가 요청된 범위에 동의하지 않았습니다.
해결 방법: 증분 동의를 .WithScopes() 사용하여 동의 흐름을 트리거합니다.
사진 404 오류 해결
원인: 사용자에게 프로필 사진이 없습니다.
해결 방법: 404를 정상적으로 처리하고 기본 아바타를 제공합니다.
일괄 처리 요청 오류 해결
원인: 일괄 처리의 개별 요청은 독립적으로 실패할 수 있습니다.
해결 방법: 각 응답을 일괄 처리로 확인하여 오류가 있는지 확인합니다.
var userResponse = await batchResponse.GetResponseByIdAsync<User>(userRequestId);
if (userResponse == null)
{
// Handle individual request failure
}
관련 콘텐츠
Next 단계: Azure SDK 또는 custom API 호출에 대해 알아봅니다.