Dado que el riesgo y la directiva se evalúan en tiempo real, algunas API de recursos pueden aumentar hasta 28 horas. Estos tokens de larga duración se actualizan de manera proactiva mediante la biblioteca de autenticación de Microsoft (MSAL), lo que aumenta la resistencia de las aplicaciones.
Para usar CAE, tanto la aplicación como la API de recursos a la que se tiene acceso deben estar habilitadas para CAE. Si una API de recursos implementa CAE y la aplicación declara que puede controlar CAE, la aplicación recibe tokens de CAE para ese recurso. Por esta razón, si declara que la aplicación está lista para CAE, la aplicación debe controlar el desafío de notificaciones CAE para todas las API de recursos que acepten tokens de acceso de identidad de Microsoft.
Sin embargo, la preparación del código para admitir recursos habilitados para CAE no limita su capacidad de trabajar con API que no admiten CAE. Si la aplicación no controla correctamente las respuestas de CAE, es posible que vuelva a intentar repetidamente una llamada API mediante un token que sea técnicamente válido, pero que se revoque debido a CAE.
Comience agregando código para controlar las respuestas de la API de recursos que rechazan la llamada debido a CAE. Con CAE, las API devuelven un estado 401 y un encabezado WWW-Authenticate
cuando se revoca el token de acceso o la API detecta un cambio en la dirección IP usada. El encabezado WWW-Authenticate
contiene un desafío de notificaciones que la aplicación puede usar para adquirir un nuevo token de acceso.
Cuando se cumplen estas condiciones, la aplicación puede extraer y descodificar el desafío de notificaciones mediante la clase MSAL.NET WwwAuthenticateParameters
.
if (APIresponse.IsSuccessStatusCode)
{
// ...
}
else
{
if (APIresponse.StatusCode == System.Net.HttpStatusCode.Unauthorized
&& APIresponse.Headers.WwwAuthenticate.Any())
{
string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(APIresponse.Headers);
A continuación, la aplicación usa el desafío de notificaciones para adquirir un nuevo token de acceso para el recurso.
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);
}
// ...
Una vez que la aplicación esté lista para controlar el desafío de notificaciones que devuelve un habilitado para CAE, puede indicar a la identidad de Microsoft que la aplicación está lista. Para hacerlo desde la aplicación MSAL, incluya las funcionalidades de cliente "cp1"
al crear su cliente público.
_clientApp = PublicClientApplicationBuilder.Create(App.ClientId)
.WithDefaultRedirectUri()
.WithAuthority(authority)
.WithClientCapabilities(new [] {"cp1"})
.Build();
Cuando se cumplen estas condiciones, la aplicación puede extraer el desafío de notificaciones del encabezado de respuesta de la API de la siguiente manera:
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;
}
A continuación, la aplicación usa el desafío de notificaciones para adquirir un nuevo token de acceso para el recurso.
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);
}
}
Una vez que la aplicación esté lista para controlar el desafío de notificaciones que devuelve un recurso habilitado para CAE, puede indicar a la identidad de Microsoft que la aplicación está lista para CAE agregando una propiedad clientCapabilities
en la configuración de MSAL.
const msalConfig = {
auth: {
clientId: 'Enter_the_Application_Id_Here',
clientCapabilities: ["CP1"]
// remaining settings...
}
}
const msalInstance = new PublicClientApplication(msalConfig);
Cuando se cumplen estas condiciones, la aplicación puede extraer el desafío de notificaciones del encabezado de respuesta de la API de la siguiente manera:
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)
Declarar el apoyo a la capacidad del cliente CP1
En la configuración de la aplicación, debe declarar que la aplicación admite CAE mediante la inclusión de la funcionalidad de cliente de CP1
. Esto se especifica utilizando la propiedad 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"
}
}
]
}
Responder a los desafíos de CAE en tiempo de ejecución
Realice una solicitud a un recurso, si la respuesta contiene un desafío de notificaciones, extráigala y vuelva a insertarla en MSAL para usarla en la siguiente solicitud.
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
Los fragmentos de código siguientes describen el flujo de adquisición de un token de forma silenciosa, la realización de una llamada http al proveedor de recursos y, a continuación, el control de un caso CAE. Es posible que se requiera una llamada de interacción adicional si se produjo un error en la llamada silenciosa con notificaciones.
Declaración de compatibilidad con la funcionalidad del cliente CP1
En la configuración de la aplicación, debe declarar que la aplicación admite CAE mediante la inclusión de la funcionalidad de cliente de CP1
. Esto se especifica mediante la propiedad 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)
Implemente una función auxiliar para analizar los desafíos de las notificaciones.
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/ desafíos de notificaciones.
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
}
Cuando se cumplen estas condiciones, la aplicación puede extraer el desafío de notificaciones del encabezado de respuesta de la API de la siguiente manera:
Anunciar funcionalidades de cliente.
client, err := New("client-id", WithAuthority(authority), WithClientCapabilities([]string{"cp1"}))
analiza el encabezado WWW-Authenticate
y pasa el desafío resultante a MSAL-Go.
// No snippet provided at this time
Intente adquirir un token de forma silenciosa con el desafío de notificaciones.
var ar AuthResult;
ar, err := client.AcquireTokenSilent(ctx, tokenScope, public.WithClaims(claims))
Para probar la aplicación, puede iniciar la sesión de un usuario mediante Azure Portal para revocar las sesiones del usuario. La próxima vez que la aplicación llame a la API habilitada para CAE, se le pedirá al usuario que vuelva a autenticarse.