Las solicitudes de token silenciosas a Microsoft Entra ID pueden producir un error por motivos como un cambio de contraseña o actualizaciones en las directivas de acceso condicional. Con más frecuencia, los errores se deben al vencimiento de la duración de 24 horas del token de actualización y a que el explorador bloquea las cookies de terceros, lo que impide el uso de iframes ocultos para seguir autenticando al usuario. En estos casos, tiene que invocar uno de los métodos interactivos para adquirir tokens (lo que puede pedirse al usuario):
Al crear la solicitud de token de acceso, puede establecer los ámbitos de API que quiere que incluya el token de acceso. Puede que no se concedan todos los ámbitos solicitados en el token de acceso. Depende del consentimiento del usuario.
En el código siguiente se combina el patrón descrito anteriormente con los métodos de una experiencia de ventana emergente:
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken);
})
.catch(function (error) {
if (error instanceof InteractionRequiredAuthError) {
publicClientApplication
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken);
})
.catch(function (error) {
console.log(error);
});
}
console.log(error);
});
En el código siguiente se combina el patrón descrito anteriormente con los métodos de una experiencia de ventana emergente:
const accessTokenRequest = {
scopes: ["user.read"],
};
userAgentApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
})
.catch(function (error) {
if (error.errorMessage.indexOf("interaction_required") !== -1) {
userAgentApplication
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
})
.catch(function (error) {
console.log(error);
});
}
console.log(error);
});
El contenedor MSAL Angular proporciona el interceptor HTTP, que adquiere de forma automática y silenciosa los tokens de acceso y los asocia a las solicitudes HTTP de las API.
Puede especificar los ámbitos de las API en la opción de configuración protectedResourceMap
. MsalInterceptor
solicitará los ámbitos especificados cuando adquiera automáticamente los tokens.
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 {}
Para conocer el éxito o fracaso de la adquisición de tokens silenciosa, MSAL Angular proporciona eventos a los que puede suscribirse. También es importante recordar esta información si quiere cancelar la suscripción.
import { MsalBroadcastService } from '@azure/msal-angular';
import { EventMessage, EventType } from '@azure/msal-browser';
import { filter, Subject, takeUntil } from 'rxjs';
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) => {
});
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
Como alternativa, también puede adquirir tokens de forma explícita mediante los métodos de adquisición de token, tal como se describe en la biblioteca principal MSAL.js.
El contenedor MSAL Angular proporciona el interceptor HTTP, que adquiere de forma automática y silenciosa los tokens de acceso y los asocia a las solicitudes HTTP de las API.
Puede especificar los ámbitos de las API en la opción de configuración protectedResourceMap
. MsalInterceptor
solicitará los ámbitos especificados cuando adquiera automáticamente los tokens.
@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 {}
Para conocer el éxito o fracaso de la adquisición de tokens silenciosa, MSAL Angular proporciona devoluciones de llamada a las que puede suscribirse. También es importante recordar esta información si quiere cancelar la suscripción.
ngOnInit() {
this.subscription = this.broadcastService.subscribe("msal:acquireTokenFailure", (payload) => {
});
}
ngOnDestroy() {
this.broadcastService.getMSALSubject().next(1);
if (this.subscription) {
this.subscription.unsubscribe();
}
}
Como alternativa, también puede adquirir tokens de forma explícita mediante los métodos de adquisición de token, tal como se describe en la biblioteca principal MSAL.js.
En el código siguiente se combina el patrón descrito anteriormente con los métodos de una experiencia de ventana emergente:
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) => {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken).then((response) => {
setApiData(response);
});
})
.catch((error) => {
if (error instanceof InteractionRequiredAuthError) {
instance
.acquireTokenPopup(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken).then((response) => {
setApiData(response);
});
})
.catch(function (error) {
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>
);
}
Como alternativa, si necesita adquirir un token fuera de un componente de React, puede llamar a acquireTokenSilent
, pero no debe recurrir a la interacción si se produce un error. Todas las interacciones deben tener lugar bajo el componente MsalProvider
en el árbol de componentes.
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken);
})
.catch(function (error) {
console.log(error);
});
El siguiente patrón es como se describió anteriormente, pero se muestra con un método de redirección para adquirir tokens de manera interactiva. Tendrá que llamar a handleRedirectPromise
y esperar cuando se cargue la página.
const redirectResponse = await publicClientApplication.handleRedirectPromise();
if (redirectResponse !== null) {
let accessToken = redirectResponse.accessToken;
callApi(accessToken);
} else {
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken);
})
.catch(function (error) {
console.log(error);
if (error instanceof InteractionRequiredAuthError) {
publicClientApplication.acquireTokenRedirect(accessTokenRequest);
}
});
}
El siguiente patrón es como se describió anteriormente, pero se muestra con un método de redirección para adquirir tokens de manera interactiva. Como se mencionó anteriormente, tendrá que registrar la devolución de llamada de redirección.
function authCallback(error, response) {
}
userAgentApplication.handleRedirectCallback(authCallback);
const accessTokenRequest: AuthenticationParameters = {
scopes: ["user.read"],
};
userAgentApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
})
.catch(function (error) {
console.log(error);
if (error.errorMessage.indexOf("interaction_required") !== -1) {
userAgentApplication.acquireTokenRedirect(accessTokenRequest);
}
});
Solicitud de notificaciones opcionales
Puede usar notificaciones opcionales con los siguientes fines:
- Incluir notificaciones adicionales en los tokens de la aplicación.
- Cambiar el comportamiento de determinadas notificaciones que la Plataforma de identidad de Microsoft Entra ID devuelve en tokens.
- Agregar notificaciones personalizadas para la aplicación y acceder a ellas.
Para solicitar notificaciones opcionales en IdToken
, puede enviar un objeto de notificaciones en cadena al campo claimsRequest
de la clase AuthenticationParameters.ts
.
var claims = {
optionalClaims: {
idToken: [
{
name: "auth_time",
essential: true,
},
],
},
};
var request = {
scopes: ["user.read"],
claimsRequest: JSON.stringify(claims),
};
myMSALObj.acquireTokenPopup(request);
Para más información, consulte Notificaciones opcionales.
Este código es el mismo que se describió anteriormente, salvo que se recomienda arrancar MsalRedirectComponent
para controlar los redireccionamientos. También se pueden cambiar las configuraciones de MsalInterceptor
para usar los redireccionamientos.
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 {}
Este código es igual al que se ha descrito anteriormente.
Si se produce un error en acquireTokenSilent
, recurra a acquireTokenRedirect
. Este método iniciará un redireccionamiento de marco completo y la respuesta se controlará al volver a la aplicación. Cuando este componente se representa después de volver del redireccionamiento, acquireTokenSilent
ahora debe ejecutarse correctamente, ya que los tokens se extraen de la memoria caché.
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) => {
let accessToken = accessTokenResponse.accessToken;
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>
);
}
Como alternativa, si necesita adquirir un token fuera de un componente de React, puede llamar a acquireTokenSilent
, pero no debe recurrir a la interacción si se produce un error. Todas las interacciones deben tener lugar bajo el componente MsalProvider
en el árbol de componentes.
const account = publicClientApplication.getAllAccounts()[0];
const accessTokenRequest = {
scopes: ["user.read"],
account: account,
};
publicClientApplication
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
let accessToken = accessTokenResponse.accessToken;
callApi(accessToken);
})
.catch(function (error) {
console.log(error);
});