Microsoft Entra ID에 대한 자동 토큰 요청은 암호 변경 또는 업데이트된 조건부 액세스 정책과 같은 이유로 실패할 수 있습니다. 대개 이러한 실패는 새로 고침 토큰의 24시간 수명이 만료되고 브라우저에서 타사 쿠키를 차단하기 때문입니다. 이로 인해 숨겨진 iframe을 사용하여 사용자를 계속 인증할 수 없습니다. 이러한 경우에는 토큰을 얻기 위해 대화형 메서드(사용자에게 메시지가 표시될 수 있음) 중 하나를 호출해야 합니다.
팝업이나 리디렉션 환경 중 선택은 애플리케이션 흐름에 따라 다릅니다.
액세스 토큰 요청을 빌드할 때 액세스 토큰에 포함하려는 API 범위를 설정할 수 있습니다. 모든 요청 범위가 액세스 토큰에 부여되지 않을 수 있습니다. 사용자 동의에 따라 다릅니다.
다음 코드에서는 앞에서 설명한 패턴과 팝업 환경의 메서드를 결합합니다.
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken);
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
if (error instanceof InteractionRequiredAuthError) {
publicClientApplication
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token interactive success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken);
})
.catch(function (error) {
// Acquire token interactive failure
console.log(error);
});
}
console.log(error);
});
다음 코드에서는 앞에서 설명한 패턴과 팝업 환경의 메서드를 결합합니다.
const accessTokenRequest = {
scopes: ["user.read"],
};
userAgentApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
// Call API with token
let accessToken = accessTokenResponse.accessToken;
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
if (error.errorMessage.indexOf("interaction_required") !== -1) {
userAgentApplication
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token interactive success
})
.catch(function (error) {
// Acquire token interactive failure
console.log(error);
});
}
console.log(error);
});
MSAL Angular 래퍼는 자동으로 액세스 토큰을 획득하고 API에 대한 HTTP 요청에 연결하는 HTTP 인터셉터를 제공합니다.
protectedResourceMap
구성 옵션에서 API 범위를 지정할 수 있습니다. MsalInterceptor
는 토큰을 자동으로 획득할 때 지정된 범위를 요청합니다.
// In app.module.ts
import { PublicClientApplication, InteractionType } from "@azure/msal-browser";
import { MsalInterceptor, MsalModule } from "@azure/msal-angular";
@NgModule({
declarations: [
// ...
],
imports: [
// ...
MsalModule.forRoot(
new PublicClientApplication({
auth: {
clientId: "Enter_the_Application_Id_Here",
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: isIE,
},
}),
{
interactionType: InteractionType.Popup,
authRequest: {
scopes: ["user.read"],
},
},
{
interactionType: InteractionType.Popup,
protectedResourceMap: new Map([
["https://graph.microsoft.com/v1.0/me", ["user.read"]],
]),
}
),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
자동 토큰 획득을 성공 및 실패할 경우 MSAL Angular는 구독할 수 있는 이벤트를 제공합니다. 또한 구독을 취소해야 합니다.
import { MsalBroadcastService } from '@azure/msal-angular';
import { EventMessage, EventType } from '@azure/msal-browser';
import { filter, Subject, takeUntil } from 'rxjs';
// In app.component.ts
export class AppComponent implements OnInit {
private readonly _destroying$ = new Subject<void>();
constructor(private broadcastService: MsalBroadcastService) { }
ngOnInit() {
this.broadcastService.msalSubject$
.pipe(
filter((msg: EventMessage) => msg.eventType === EventType.ACQUIRE_TOKEN_SUCCESS),
takeUntil(this._destroying$)
)
.subscribe((result: EventMessage) => {
// Do something with event payload here
});
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
또는 핵심 MSAL.js 라이브러리의 설명대로 acquire-token 메서드를 사용하여 토큰을 명시적으로 획득할 수 있습니다.
MSAL Angular 래퍼는 자동으로 액세스 토큰을 획득하고 API에 대한 HTTP 요청에 연결하는 HTTP 인터셉터를 제공합니다.
protectedResourceMap
구성 옵션에서 API 범위를 지정할 수 있습니다. MsalInterceptor
는 토큰을 자동으로 획득할 때 지정된 범위를 요청합니다.
// app.module.ts
@NgModule({
declarations: [
// ...
],
imports: [
// ...
MsalModule.forRoot(
{
auth: {
clientId: "Enter_the_Application_Id_Here",
},
},
{
popUp: !isIE,
consentScopes: ["user.read", "openid", "profile"],
protectedResourceMap: [
["https://graph.microsoft.com/v1.0/me", ["user.read"]],
],
}
),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
자동 토큰 획득을 성공 및 실패할 경우 MSAL Angular는 구독할 수 있는 콜백을 제공합니다. 또한 구독을 취소해야 합니다.
// In app.component.ts
ngOnInit() {
this.subscription = this.broadcastService.subscribe("msal:acquireTokenFailure", (payload) => {
});
}
ngOnDestroy() {
this.broadcastService.getMSALSubject().next(1);
if (this.subscription) {
this.subscription.unsubscribe();
}
}
또는 핵심 MSAL.js 라이브러리의 설명대로 acquire-token 메서드를 사용하여 토큰을 명시적으로 획득할 수 있습니다.
다음 코드에서는 앞에서 설명한 패턴과 팝업 환경의 메서드를 결합합니다.
import {
InteractionRequiredAuthError,
InteractionStatus,
} from "@azure/msal-browser";
import { AuthenticatedTemplate, useMsal } from "@azure/msal-react";
function ProtectedComponent() {
const { instance, inProgress, accounts } = useMsal();
const [apiData, setApiData] = useState(null);
useEffect(() => {
if (!apiData && inProgress === InteractionStatus.None) {
const accessTokenRequest = {
scopes: ["user.read"],
account: accounts[0],
};
instance
.acquireTokenSilent(accessTokenRequest)
.then((accessTokenResponse) => {
// Acquire token silent success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken).then((response) => {
setApiData(response);
});
})
.catch((error) => {
if (error instanceof InteractionRequiredAuthError) {
instance
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token interactive success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken).then((response) => {
setApiData(response);
});
})
.catch(function (error) {
// Acquire token interactive failure
console.log(error);
});
}
console.log(error);
});
}
}, [instance, accounts, inProgress, apiData]);
return <p>Return your protected content here: {apiData}</p>;
}
function App() {
return (
<AuthenticatedTemplate>
<ProtectedComponent />
</AuthenticatedTemplate>
);
}
또는 React 구성 요소 외부에서 토큰을 획득해야 하는 경우 acquireTokenSilent
를 호출할 수 있습니다. 하지만 실패한 경우 상호 작용으로 대체해서는 안 됩니다. 모든 상호 작용은 구성 요소 트리의 MsalProvider
구성 요소 아래에서 발생해야 합니다.
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
// Use the same publicClientApplication instance provided to MsalProvider
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken);
})
.catch(function (error) {
//Acquire token silent failure
console.log(error);
});
다음 패턴은 앞에서 설명되었지만 대화형으로 토큰을 획득하도록 리디렉션 메서드로 표시됩니다. 페이지 로드 시 handleRedirectPromise
를 호출하고 기다려야 합니다.
const redirectResponse = await publicClientApplication.handleRedirectPromise();
if (redirectResponse !== null) {
// Acquire token silent success
let accessToken = redirectResponse.accessToken;
// Call your API with token
callApi(accessToken);
} else {
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
// Call API with token
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken);
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
console.log(error);
if (error instanceof InteractionRequiredAuthError) {
publicClientApplication.acquireTokenRedirect(accessTokenRequest);
}
});
}
다음 패턴은 앞에서 설명되었지만 대화형으로 토큰을 획득하도록 리디렉션 메서드로 표시됩니다. 앞에서의 설명대로 리디렉션 콜백을 등록해야 합니다.
function authCallback(error, response) {
// Handle redirect response
}
userAgentApplication.handleRedirectCallback(authCallback);
const accessTokenRequest: AuthenticationParameters = {
scopes: ["user.read"],
};
userAgentApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
// Call API with token
let accessToken = accessTokenResponse.accessToken;
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
console.log(error);
if (error.errorMessage.indexOf("interaction_required") !== -1) {
userAgentApplication.acquireTokenRedirect(accessTokenRequest);
}
});
선택적 클레임 요청
선택적 클레임을 다음 목적으로 사용할 수 있습니다.
- 애플리케이션의 토큰에 추가 클레임을 포함합니다.
- Microsoft Entra ID에서 토큰에 반환하는 특정 클레임의 동작을 변경합니다.
- 애플리케이션에 대한 사용자 지정 클레임을 추가하고 액세스합니다.
IdToken
에서 선택적 클레임을 요청하려면 stringified 클레임 개체를 AuthenticationParameters.ts
클래스의 claimsRequest
필드로 전송하면 됩니다.
var claims = {
optionalClaims: {
idToken: [
{
name: "auth_time",
essential: true,
},
],
},
};
var request = {
scopes: ["user.read"],
claimsRequest: JSON.stringify(claims),
};
myMSALObj.acquireTokenPopup(request);
자세한 내용은 선택적 클레임을 참조하세요.
이 코드는 앞에서의 설명과 동일하지만 리디렉션을 처리하도록 MsalRedirectComponent
를 부트스트랩하는 것이 좋다는 점이 다릅니다. 리디렉션을 사용하도록 MsalInterceptor
구성을 변경할 수도 있습니다.
// In app.module.ts
import { PublicClientApplication, InteractionType } from "@azure/msal-browser";
import {
MsalInterceptor,
MsalModule,
MsalRedirectComponent,
} from "@azure/msal-angular";
@NgModule({
declarations: [
// ...
],
imports: [
// ...
MsalModule.forRoot(
new PublicClientApplication({
auth: {
clientId: "Enter_the_Application_Id_Here",
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: isIE,
},
}),
{
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ["user.read"],
},
},
{
interactionType: InteractionType.Redirect,
protectedResourceMap: new Map([
["https://graph.microsoft.com/v1.0/me", ["user.read"]],
]),
}
),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
],
bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}
acquireTokenSilent
가 실패하면 acquireTokenRedirect
로 대체합니다. 이 메서드는 전체 프레임 리디렉션을 시작하고 애플리케이션으로 돌아갈 때 응답이 처리됩니다. 이 구성 요소가 리디렉션에서 반환된 후 렌더링되면 acquireTokenSilent
는 이제 캐시에서 토큰을 가져오므로 성공해야 합니다.
import {
InteractionRequiredAuthError,
InteractionStatus,
} from "@azure/msal-browser";
import { AuthenticatedTemplate, useMsal } from "@azure/msal-react";
function ProtectedComponent() {
const { instance, inProgress, accounts } = useMsal();
const [apiData, setApiData] = useState(null);
useEffect(() => {
const accessTokenRequest = {
scopes: ["user.read"],
account: accounts[0],
};
if (!apiData && inProgress === InteractionStatus.None) {
instance
.acquireTokenSilent(accessTokenRequest)
.then((accessTokenResponse) => {
// Acquire token silent success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken).then((response) => {
setApiData(response);
});
})
.catch((error) => {
if (error instanceof InteractionRequiredAuthError) {
instance.acquireTokenRedirect(accessTokenRequest);
}
console.log(error);
});
}
}, [instance, accounts, inProgress, apiData]);
return <p>Return your protected content here: {apiData}</p>;
}
function App() {
return (
<AuthenticatedTemplate>
<ProtectedComponent />
</AuthenticatedTemplate>
);
}
또는 React 구성 요소 외부에서 토큰을 획득해야 하는 경우 acquireTokenSilent
를 호출할 수 있습니다. 하지만 실패한 경우 상호 작용으로 대체해서는 안 됩니다. 모든 상호 작용은 구성 요소 트리의 MsalProvider
구성 요소 아래에서 발생해야 합니다.
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
// Use the same publicClientApplication instance provided to MsalProvider
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
let accessToken = accessTokenResponse.accessToken;
// Call your API with token
callApi(accessToken);
})
.catch(function (error) {
//Acquire token silent failure
console.log(error);
});