위험 및 정책이 실시간으로 평가되므로 일부 리소스 API 토큰 수명은 최대 28시간까지 증가할 수 있습니다. 이러한 수명이 긴 토큰은 MSAL(Microsoft 인증 라이브러리)에서 사전에 새로 고쳐 애플리케이션의 복원력을 높입니다.
CAE를 사용하려면 앱 및 액세스하는 리소스 API 모두 CAE를 사용하도록 설정해야 합니다. 리소스 API가 CAE을 구현하고 애플리케이션에서 CAE를 처리할 수 있다고 선언하는 경우 앱에 해당 리소스에 대한 CAE 토큰이 제공됩니다. 이러한 이유로 앱 CAE 준비를 선언하는 경우 애플리케이션은 Microsoft ID 액세스 토큰을 수락하는 모든 리소스 API에 대한 CAE 클레임 챌린지를 처리해야 합니다.
그러나 CAE 지원 리소스를 지원하도록 코드를 준비해도 CAE를 지원하지 않는 API를 사용하는 기능이 제한되지는 않습니다. 앱이 CAE 응답을 올바르게 처리하지 않는 경우 기술적으로 유효하지만 CAE로 인해 해지된 토큰을 사용하여 API 호출을 반복적으로 다시 시도할 수 있습니다.
먼저 CAE로 인해 호출을 거부하는 리소스 API의 응답을 처리하는 코드를 추가합니다. CAE를 사용하면 API는 액세스 토큰이 해지되거나 API가 사용된 IP 주소의 변경 사항을 검색할 때 401 상태와 WWW-Authenticate
헤더를 반환합니다. 헤더에는 WWW-Authenticate
애플리케이션이 새 액세스 토큰을 획득하는 데 사용할 수 있는 클레임 챌린지가 포함되어 있습니다.
앱에서 다음을 확인합니다.
이러한 조건이 충족되면 앱은 MSAL.NET WwwAuthenticateParameters
클래스를 사용하여 클레임 챌린지를 추출하고 디코딩할 수 있습니다.
if (APIresponse.IsSuccessStatusCode)
{
// ...
}
else
{
if (APIresponse.StatusCode == System.Net.HttpStatusCode.Unauthorized
&& APIresponse.Headers.WwwAuthenticate.Any())
{
string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(APIresponse.Headers);
그런 다음 앱은 클레임 챌린지를 사용하여 리소스에 대한 새 액세스 토큰을 획득합니다.
try
{
authResult = await _clientApp.AcquireTokenSilent(scopes, firstAccount)
.WithClaims(claimChallenge)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
try
{
authResult = await _clientApp.AcquireTokenInteractive(scopes)
.WithClaims(claimChallenge)
.WithAccount(firstAccount)
.ExecuteAsync()
.ConfigureAwait(false);
}
// ...
애플리케이션이 CAE 사용 리소스에서 반환된 클레임 챌린지를 처리할 준비가 되면 앱이 CAE 준비 상태임을 Microsoft ID에 알릴 수 있습니다. MSAL 애플리케이션에서 이 작업을 수행하려면 클라이언트 기능을 사용하여 공용 클라이언트를 빌드합니다 "cp1"
.
_clientApp = PublicClientApplicationBuilder.Create(App.ClientId)
.WithDefaultRedirectUri()
.WithAuthority(authority)
.WithClientCapabilities(new [] {"cp1"})
.Build();
이러한 조건이 충족되면 앱은 다음과 같이 API 응답 헤더에서 클레임 챌린지를 추출할 수 있습니다.
try {
const response = await fetch(apiEndpoint, options);
if (response.status === 401 && response.headers.get('www-authenticate')) {
const authenticateHeader = response.headers.get('www-authenticate');
const claimsChallenge = parseChallenges(authenticateHeader).claims;
// use the claims challenge to acquire a new access token...
}
} catch(error) {
// ...
}
// helper function to parse the www-authenticate header
function parseChallenges(header) {
const schemeSeparator = header.indexOf(' ');
const challenges = header.substring(schemeSeparator + 1).split(',');
const challengeMap = {};
challenges.forEach((challenge) => {
const [key, value] = challenge.split('=');
challengeMap[key.trim()] = window.decodeURI(value.replace(/['"]+/g, ''));
});
return challengeMap;
}
그런 다음 앱은 클레임 챌린지를 사용하여 리소스에 대한 새 액세스 토큰을 획득합니다.
const tokenRequest = {
claims: window.atob(claimsChallenge), // decode the base64 string
scopes: ['User.Read'],
account: msalInstance.getActiveAccount()
};
let tokenResponse;
try {
tokenResponse = await msalInstance.acquireTokenSilent(tokenRequest);
} catch (error) {
if (error instanceof InteractionRequiredAuthError) {
tokenResponse = await msalInstance.acquireTokenPopup(tokenRequest);
}
}
애플리케이션이 CAE 사용 리소스에서 반환된 클레임 챌린지를 처리할 준비가 되면 MSAL 구성에 clientCapabilities
속성을 추가하여 앱에 CAE가 준비되었다고 Microsoft Identity에 알릴 수 있습니다.
const msalConfig = {
auth: {
clientId: 'Enter_the_Application_Id_Here',
clientCapabilities: ["CP1"]
// remaining settings...
}
}
const msalInstance = new PublicClientApplication(msalConfig);
이러한 조건이 충족되면 앱은 다음과 같이 API 응답 헤더에서 클레임 챌린지를 추출할 수 있습니다.
import msal # pip install msal
import requests # pip install requests
import www_authenticate # pip install www-authenticate==0.9.2
# Once your application is ready to handle the claim challenge returned by a CAE-enabled resource, you can tell Microsoft Identity your app is CAE-ready. To do this in your MSAL application, build your Public Client using the Client Capabilities of "cp1".
app = msal.PublicClientApplication("your_client_id", client_capabilities=["cp1"])
...
# When these conditions are met, the app can extract the claims challenge from the API response header as follows:
response = requests.get("<your_resource_uri_here>")
if response.status_code == 401 and response.headers.get('WWW-Authenticate'):
parsed = www_authenticate.parse(response.headers['WWW-Authenticate'])
claims = parsed.get("bearer", {}).get("claims")
# Your app would then use the claims challenge to acquire a new access token for the resource.
if claims:
auth_result = app.acquire_token_interactive(["scope"], claims_challenge=claims)
CP1 클라이언트 기능에 대한 지원 선언
애플리케이션 구성에서 클라이언트 기능을 포함하여 CP1
애플리케이션이 CAE를 지원한다고 선언해야 합니다. JSON 속성을 사용하여 client_capabilities
지정합니다.
{
"client_id" : "<your_client_id>",
"authorization_user_agent" : "DEFAULT",
"redirect_uri" : "msauth://<pkg>/<cert_hash>",
"multiple_clouds_supported":true,
"broker_redirect_uri_registered": true,
"account_mode": "MULTIPLE",
"client_capabilities": "CP1",
"authorities" : [
{
"type": "AAD",
"audience": {
"type": "AzureADandPersonalMicrosoftAccount"
}
}
]
}
런타임 시 CAE 챌린지에 응답
응답에 클레임 챌린지가 포함된 경우 리소스에 대한 요청을 수행하고, 추출한 다음, 다음 요청에서 사용하기 위해 MSAL에 다시 공급합니다.
final HttpURLConnection connection = ...;
final int responseCode = connection.getResponseCode();
// Check the response code...
if (200 == responseCode) {
// ...
} else if (401 == responseCode) {
final String authHeader = connection.getHeaderField("WWW-Authenticate");
if (null != authHeader) {
final ClaimsRequest claimsRequest = WWWAuthenticateHeader
.getClaimsRequestFromWWWAuthenticateHeaderValue(authHeader);
// Feed the challenge back into MSAL, first silently, then interactively if required
final AcquireTokenSilentParameters silentParameters = new AcquireTokenSilentParameters.Builder()
.fromAuthority(authority)
.forAccount(account)
.withScopes(scope)
.withClaims(claimsRequest)
.build();
try {
final IAuthenticationResult silentRequestResult = mPublicClientApplication.acquireTokenSilent(silentParameters);
// If successful - your business logic goes here...
} catch (final Exception e) {
if (e instanceof MsalUiRequiredException) {
// Retry the request interactively, passing in any claims challenge...
}
}
}
} else {
// ...
}
// Don't forget to close your connection
다음 코드 조각에서는 토큰을 자동으로 획득하고, 리소스 공급자를 http로 호출한 다음, CAE 사례를 처리하는 흐름을 설명합니다. 자동 호출이 클레임과 함께 실패한 경우 추가 상호 작용 호출이 필요할 수 있습니다.
CP1 클라이언트 기능에 대한 지원 선언
애플리케이션 구성에서 클라이언트 기능을 포함하여 CP1
애플리케이션이 CAE를 지원한다고 선언해야 합니다. 이 속성은 속성을 사용하여 지정됩니다 clientCapabilities
.
let clientConfigurations = MSALPublicClientApplicationConfig(clientId: "contoso-app-ABCDE-12345",
redirectUri: "msauth.com.contoso.appbundle://auth",
authority: try MSALAuthority(url: URL(string: "https://login.microsoftonline.com/organizations")!))
clientConfigurations.clientApplicationCapabilities = ["CP1"]
let applicationContext = try MSALPublicClientApplication(configuration: clientConfigurations)
클레임 챌린지 구문 분석을 위한 도우미 함수를 구현합니다.
func parsewwwAuthenticateHeader(headers:Dictionary<AnyHashable, Any>) -> String? {
// !! This is a sample code and is not validated, please provide your own implementation or fully test the sample code provided here.
// Can also refer here for our internal implementation: https://github.com/AzureAD/microsoft-authentication-library-common-for-objc/blob/dev/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m#L112
guard let wwwAuthenticateHeader = headers["WWW-Authenticate"] as? String else {
// did not find the header, handle gracefully
return nil
}
var parameters = [String: String]()
// regex mapping
let regex = try! NSRegularExpression(pattern: #"(\w+)="([^"]*)""#)
let matches = regex.matches(in: wwwAuthenticateHeader, range: NSRange(wwwAuthenticateHeader.startIndex..., in: wwwAuthenticateHeader))
for match in matches {
if let keyRange = Range(match.range(at: 1), in: wwwAuthenticateHeader),
let valueRange = Range(match.range(at: 2), in: wwwAuthenticateHeader) {
let key = String(wwwAuthenticateHeader[keyRange])
let value = String(wwwAuthenticateHeader[valueRange])
parameters[key] = value
}
}
guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) else {
// cannot convert params into json date, end gracefully
return nil
}
return String(data: jsonData, encoding: .utf8)
}
Catch & parse 401 /claims challenges.
let response = .... // HTTPURLResponse object from 401'd service response
switch response.statusCode {
case 200:
// ...succeeded!
break
case 401:
let headers = response.allHeaderFields
// Parse header fields
guard let wwwAuthenticateHeaderString = self.parsewwwAuthenticateHeader(headers: headers) else {
// 3.7 no valid wwwAuthenticateHeaderString is returned from header, end gracefully
return
}
let claimsRequest = MSALClaimsRequest(jsonString: wwwAuthenticateHeaderString, error: nil)
// Create claims request
let parameters = MSALSilentTokenParameters(scopes: "Enter_the_Protected_API_Scopes_Here", account: account)
parameters.claimsRequest = claimsRequest
// Acquire token silently again with the claims challenge
applicationContext.acquireTokenSilent(with: parameters) { (result, error) in
if let error = error {
// error happened end flow gracefully, and handle error. (e.g. interaction required)
return
}
guard let result = result else {
// no result end flow gracefully
return
}
// Success - You got a token!
}
break
default:
break
}
이러한 조건이 충족되면 앱은 다음과 같이 API 응답 헤더에서 클레임 챌린지를 추출할 수 있습니다.
클라이언트 기능을 보급합니다.
client, err := New("client-id", WithAuthority(authority), WithClientCapabilities([]string{"cp1"}))
헤더를 WWW-Authenticate
구문 분석하고 결과 챌린지를 MSAL-Go에 전달합니다.
// No snippet provided at this time
클레임 챌린지를 사용하여 토큰을 자동으로 획득하려고 시도합니다.
var ar AuthResult;
ar, err := client.AcquireTokenSilent(ctx, tokenScope, public.WithClaims(claims))
사용자를 로그인한 다음, Azure Portal를 사용해 사용자 세션을 취소하여 애플리케이션을 테스트할 수 있습니다. 다음에 앱에서 CAE 사용 API를 호출하면 사용자에게 다시 인증하라는 메시지가 표시됩니다.