클라이언트 애플리케이션 개체를 빌드했습니다. 이제 이를 사용하여 웹 API를 호출하기 위한 토큰을 획득합니다. ASP.NET 또는 ASP.NET Core에서 웹 API 호출은 컨트롤러에서 수행됩니다.
Microsoft.Identity.Web은 Microsoft Graph 또는 다운스트림 웹 API를 호출하기 위한 편리한 서비스를 제공하는 확장 메서드를 추가합니다. 해당 메서드는 웹 API를 호출하는 웹앱: API 호출에 자세히 설명되어 있습니다. 이러한 도우미 메서드를 사용하면 토큰을 수동으로 가져올 필요가 없습니다.
그러나 토큰을 수동으로 가져오려는 경우 다음 코드는 home 컨트롤러에서 Microsoft.Identity.Web을 사용하여 이 작업을 수행하는 예를 보여 줍니다. 이 코드는 Microsoft Graph SDK 대신 REST API를 사용하여 Microsoft Graph를 호출합니다. 일반적으로 토큰을 가져올 필요는 없으며 요청에 추가할 인증 헤더를 빌드해야 합니다. 인증 헤더를 가져오려면 컨트롤러 생성자(또는 Blazor를 사용하는 경우 페이지 생성자)에서 종속성 삽입을 통해 IAuthorizationHeaderProvider
서비스를 삽입하고 이를 컨트롤러 작업에서 사용합니다. 이 인터페이스에는 프로토콜(전달자, Pop 등)과 토큰이 포함된 문자열을 생성하는 메서드가 있습니다. 사용자를 대신하여 API를 호출하기 위한 인증 헤더를 가져오려면 (CreateAuthorizationHeaderForUserAsync
)를 사용합니다. 애플리케이션 자체를 대신하여 다운스트림 API를 호출하기 위한 인증 헤더를 가져오려면 디먼 시나리오에서(CreateAuthorizationHeaderForAppAsync
)를 사용합니다.
컨트롤러 메서드는 인증된 사용자만 웹앱을 사용할 수 있도록 하는 [Authorize]
특성에 의해 보호됩니다.
[Authorize]
public class HomeController : Controller
{
readonly IAuthorizationHeaderProvider authorizationHeaderProvider;
public HomeController(IAuthorizationHeaderProvider authorizationHeaderProvider)
{
this.authorizationHeaderProvider = authorizationHeaderProvider;
}
// Code for the controller actions (see code below)
}
ASP.NET Core에서는 종속성 삽입을 통해 IAuthorizationHeaderProvider
를 사용할 수 있습니다.
다음은 Microsoft Graph를 호출할 토큰을 가져오는 HomeController
동작의 간소화된 코드입니다.
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Acquire the access token.
string[] scopes = new string[]{"user.read"};
string accessToken = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", accessToken);
string json = await client.GetStringAsync(url);
}
이 시나리오에 필요한 코드를 더 잘 이해하려면 ms-identity-aspnetcore-webapp-tutorial 자습서의 2단계(2-1-웹앱에서 Microsoft Graph를 호출)를 참조하세요.
컨트롤러 작업 위에 있는 AuthorizeForScopes
특성(Razor 템플릿을 사용하는 경우에는 Razor 템플릿)은 Microsoft.Identity.Web에서 제공합니다. 필요한 경우 사용자에게 증분 방식으로 동의를 요청합니다.
다음과 같은 복잡한 변형도 있습니다.
- 여러 API 호출
- 증분 동의 및 조건부 액세스 처리
이러한 고급 단계는 3-WebApp-multi-APIs 자습서의 3장에서 다룹니다.
ASP.NET 코드는 ASP.NET Core에서 살펴본 코드와 비슷합니다.
[Authorize]
특성으로 보호되는 컨트롤러 작업은 컨트롤러의 ClaimsPrincipal
멤버의 테넌트 ID와 사용자 ID를 추출합니다(ASP.NET에서는 HttpContext.User
를 사용함). 이렇게 하면 인증된 사용자만 앱을 사용할 수 있습니다.
Microsoft.Identity.Web은 Microsoft Graph 또는 다운스트림 웹 API를 호출하거나 인증 헤더 또는 토큰을 가져오기 위한 편의 서비스를 제공하는 확장 메서드를 컨트롤러에 추가합니다. API를 직접 호출하는 데 사용되는 메서드는 웹 API를 호출하는 웹앱: API 호출에 자세히 설명되어 있습니다. 이러한 도우미 메서드를 사용하면 토큰을 수동으로 가져올 필요가 없습니다.
그러나 수동으로 토큰을 획득하거나 인증 헤더를 빌드하려는 경우 다음 코드는 컨트롤러에서 Microsoft.Identity.Web을 사용하여 이를 수행하는 방법을 보여 줍니다. Microsoft Graph SDK 대신 REST API를 사용하여 API(Microsoft Graph)를 호출합니다.
인증 헤더를 가져오려면 확장 메서드 GetAuthorizationHeaderProvider
를 사용하여 컨트롤러에서 IAuthorizationHeaderProvider
서비스를 가져옵니다. 사용자를 대신하여 API를 호출하기 위한 인증 헤더를 가져오려면(CreateAuthorizationHeaderForUserAsync
)를 사용합니다. 애플리케이션 자체를 대신하여 다운스트림 API를 호출하기 위한 인증 헤더를 가져오려면 디먼 시나리오에서(CreateAuthorizationHeaderForAppAsync
)를 사용합니다.
다음 코드 조각은 Microsoft Graph를 REST API로 호출하기 위한 인증 헤더를 가져오는 HomeController
의 작업을 보여 줍니다.
[Authorize]
public class HomeController : Controller
{
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Get an authorization header.
IAuthorizationHeaderProvider authorizationHeaderProvider = this.GetAuthorizationHeaderProvider();
string[] scopes = new string[]{"user.read"};
string authorizationHeader = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
string json = await client.GetStringAsync(url);
}
}
다음 코드 조각은 Microsoft Graph를 REST API로 호출하기 위한 액세스 토큰을 가져오는 HomeController
의 작업을 보여 줍니다.
[Authorize]
public class HomeController : Controller
{
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Get an authorization header.
ITokenAcquirer tokenAcquirer = TokenAcquirerFactory.GetDefaultInstance().GetTokenAcquirer();
string[] scopes = new string[]{"user.read"};
string token = await tokenAcquirer.GetTokenForUserAsync(scopes);
// Use the access token to call a protected web API.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
string json = await client.GetStringAsync(url);
}
}
Java 샘플에서 API를 호출하는 코드는 AuthPageController.java#L62의 getUsersFromGraph
메서드에 있습니다.
메서드는 getAuthResultBySilentFlow
를 호출하려고 시도합니다. 사용자가 더 많은 범위에 동의해야 하는 경우 코드는 MsalInteractionRequiredException
개체를 처리하여 사용자에게 챌린지를 발행합니다.
@RequestMapping("/msal4jsample/graph/me")
public ModelAndView getUserFromGraph(HttpServletRequest httpRequest, HttpServletResponse response)
throws Throwable {
IAuthenticationResult result;
ModelAndView mav;
try {
result = authHelper.getAuthResultBySilentFlow(httpRequest, response);
} catch (ExecutionException e) {
if (e.getCause() instanceof MsalInteractionRequiredException) {
// If the silent call returns MsalInteractionRequired, redirect to authorization endpoint
// so user can consent to new scopes.
String state = UUID.randomUUID().toString();
String nonce = UUID.randomUUID().toString();
SessionManagementHelper.storeStateAndNonceInSession(httpRequest.getSession(), state, nonce);
String authorizationCodeUrl = authHelper.getAuthorizationCodeUrl(
httpRequest.getParameter("claims"),
"User.Read",
authHelper.getRedirectUriGraph(),
state,
nonce);
return new ModelAndView("redirect:" + authorizationCodeUrl);
} else {
mav = new ModelAndView("error");
mav.addObject("error", e);
return mav;
}
}
if (result == null) {
mav = new ModelAndView("error");
mav.addObject("error", new Exception("AuthenticationResult not found in session."));
} else {
mav = new ModelAndView("auth_page");
setAccountInfo(mav, httpRequest);
try {
mav.addObject("userInfo", getUserInfoFromGraph(result.accessToken()));
return mav;
} catch (Exception e) {
mav = new ModelAndView("error");
mav.addObject("error", e);
}
}
return mav;
}
// Code omitted here
Node.js 샘플에서 토큰을 획득하는 코드는 AuthProvider
클래스의 acquireToken
메서드에 있습니다.
acquireToken(options = {}) {
return async (req, res, next) => {
try {
const msalInstance = this.getMsalInstance(this.msalConfig);
/**
* If a token cache exists in the session, deserialize it and set it as the
* cache for the new MSAL CCA instance. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
if (req.session.tokenCache) {
msalInstance.getTokenCache().deserialize(req.session.tokenCache);
}
const tokenResponse = await msalInstance.acquireTokenSilent({
account: req.session.account,
scopes: options.scopes || [],
});
/**
* On successful token acquisition, write the updated token
* cache back to the session. For more, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
*/
req.session.tokenCache = msalInstance.getTokenCache().serialize();
req.session.accessToken = tokenResponse.accessToken;
req.session.idToken = tokenResponse.idToken;
req.session.account = tokenResponse.account;
res.redirect(options.successRedirect);
} catch (error) {
if (error instanceof msal.InteractionRequiredAuthError) {
return this.login({
scopes: options.scopes || [],
redirectUri: options.redirectUri,
successRedirect: options.successRedirect || '/',
})(req, res, next);
}
next(error);
}
};
}
그런 다음 이 액세스 토큰은 /profile
엔드포인트에 대한 요청을 처리하는 데 사용됩니다.
router.get('/profile',
isAuthenticated, // check if user is authenticated
async function (req, res, next) {
try {
const graphResponse = await fetch(GRAPH_ME_ENDPOINT, req.session.accessToken);
res.render('profile', { profile: graphResponse });
} catch (error) {
next(error);
}
}
);
Python 샘플에서 API를 호출하는 코드는 app.py에 있습니다.
이 코드는 토큰 캐시에서 토큰을 얻으려고 시도합니다. 토큰을 가져올 수 없으면 사용자를 로그인 경로로 리디렉션합니다. 그렇지 않으면 API 호출을 진행할 수 있습니다.
@app.route("/call_downstream_api")
def call_downstream_api():
token = auth.get_token_for_user(app_config.SCOPE)
if "error" in token:
return redirect(url_for("login"))
# Use access token to call downstream api
api_result = requests.get(
app_config.ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']},
timeout=30,
).json()
return render_template('display.html', result=api_result)