Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article explique comment configurer le consentement groupé pour les applications Microsoft Entra ID.
Symptômes
Vous disposez d’une application cliente personnalisée et d’une application API personnalisée, et vous créez des inscriptions d’applications pour les deux applications dans Microsoft Entra ID. Vous configurez le consentement groupé pour les deux applications. Dans ce scénario, vous pouvez recevoir l’un des messages d’erreur suivants lorsque vous essayez de vous connecter à l’une ou l’autre application :
AADSTS70000 : la demande a été refusée, car une ou plusieurs étendues demandées ne sont pas autorisées ou expirées. L’utilisateur doit d’abord se connecter et accorder à l’application cliente l’accès à l’étendue demandée.
AADSTS650052 : l’application tente d’accéder à un service\"{app_id}\"(\"app_name\ ») pour laquelle votre organisation %\"{organization}\ » ne dispose pas d’un principal de service. Contactez votre administrateur informatique pour passer en revue la configuration de vos abonnements de service ou donner votre consentement à l’application pour créer le principal de service requis.
Solution
Étape 1 : Configurer knownClientApplications pour l’inscription de l’application API
Ajoutez l’ID d’application cliente personnalisée à la propriété de l’inscription d’application knownClientApplications
API personnalisée. Pour plus d’informations, consultez l’attribut knownClientApplications.
Étape 2 : Configurer les autorisations d’API
Assurez-vous que :
- Toutes les autorisations d'API requises sont correctement configurées sur les inscriptions du client personnalisé et de l'application API personnalisée.
- L’inscription d’application cliente personnalisée inclut les autorisations d’API définies dans l’inscription d’application API personnalisée.
Étape 3 : Demande de connexion
Votre demande d’authentification doit utiliser l’étendue .default
de Microsoft Graph. Pour les comptes Microsoft, l’étendue doit être pour l’API personnalisée.
Exemple de demande pour les comptes Microsoft et les comptes professionnels ou scolaires
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?response_type=code
&Client_id=72333f42-5078-4212-abb2-e4f9521ec76a
&redirect_uri=https://localhost
&scope=openid profile offline_access app_uri_id1/.default
&prompt=consent
Remarque
Le client semble manquer d’autorisation pour l’API. Cette condition est attendue, car le client est répertorié en tant que knownClientApplication
.
Exemple de demande de comptes professionnels ou scolaires uniquement
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?response_type=code
&client_id=72333f42-5078-4212-abb2-e4f9521ec76a
&redirect_uri=https://localhost
&scope=openid profile offline_access User.Read https://graph.microsoft.com/.default
&prompt=consent
Implémentation à l’aide de MSAL.NET
String[] consentScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
var loginResult = await clientApp.AcquireTokenInteractive(consentScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
La propagation du consentement pour les nouveaux principaux de service et les autorisations peut nécessiter un certain temps. Votre application doit gérer correctement ce délai.
Acquérir des jetons pour plusieurs ressources
Si votre application cliente doit acquérir des jetons pour une autre ressource, telle que Microsoft Graph, vous devez implémenter une logique pour gérer les retards potentiels une fois que les utilisateurs ont consenti à l’application. Voici quelques recommandations :
- Utilisez l’étendue
.default
lorsque vous demandez des jetons. - Assurez le suivi des périmètres acquis jusqu'à obtenir celui requis.
- Ajoutez un délai si le résultat n’a toujours pas l’étendue requise.
Actuellement, si AcquireTokenSilent
échoue, MSAL requiert une authentification interactive réussie avant de permettre une nouvelle acquisition silencieuse de jeton. Cette restriction s’applique même si un jeton d’actualisation valide est disponible.
Voici un exemple de code qui utilise la logique de nouvelle tentative :
public static async Task<AuthenticationResult> GetTokenAfterConsentAsync(string[] resourceScopes)
{
AuthenticationResult result = null;
int retryCount = 0;
int index = resourceScopes[0].LastIndexOf("/");
string resource = String.Empty;
// Determine resource of scope
if (index < 0)
{
resource = "https://graph.microsoft.com";
}
else
{
resource = resourceScopes[0].Substring(0, index);
}
string[] defaultScope = { $"{resource}/.default" };
string[] acquiredScopes = { "" };
string[] scopes = defaultScope;
while (!acquiredScopes.Contains(resourceScopes[0]) && retryCount <= 15)
{
try
{
result = await clientApp.AcquireTokenSilent(scopes, CurrentAccount).WithForceRefresh(true).ExecuteAsync();
acquiredScopes = result.Scopes.ToArray();
if (acquiredScopes.Contains(resourceScopes[0])) continue;
}
catch (Exception e)
{ }
// Switch scopes to pass to MSAL on next loop. This tricks MSAL to force AcquireTokenSilent after failure. This also resolves intermittent cachine issue in ESTS
scopes = scopes == resourceScopes ? defaultScope : resourceScopes;
retryCount++;
// Obvisouly something went wrong
if(retryCount==15)
{
throw new Exception();
}
// MSA tokens do not return scope in expected format when .default is used
int i = 0;
foreach(var acquiredScope in acquiredScopes)
{
if(acquiredScope.IndexOf('/')==0) acquiredScopes[i].Replace("/", $"{resource}/");
i++;
}
Thread.Sleep(2000);
}
return result;
}
À propos de l’API personnalisée qui utilise le flux On-behalf-of
Comme pour l’application cliente, lorsque votre API personnalisée tente d’acquérir des jetons pour une autre ressource à l’aide du flux on-Behalf-Of (OBO), il peut échouer immédiatement après le consentement. Pour résoudre ce problème, vous pouvez implémenter la logique de nouvelle tentative et le suivi de l’étendue, comme dans l’exemple de code suivant :
while (result == null && retryCount >= 6)
{
UserAssertion assertion = new UserAssertion(accessToken);
try
{
result = await apiMsalClient.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
}
catch { }
retryCount++;
if (result == null)
{
Thread.Sleep(1000 * retryCount * 2);
}
}
If (result==null) return new HttpStatusCodeResult(HttpStatusCode.Forbidden, "Need Consent");
Si toutes les nouvelles tentatives échouent, retournez un message d’erreur, puis demandez au client de démarrer un processus de consentement complet.
Exemple de code client qui suppose que votre API lève une version 403
HttpResponseMessage apiResult = null;
apiResult = await MockApiCall(result.AccessToken);
if(apiResult.StatusCode==HttpStatusCode.Forbidden)
{
var authResult = await clientApp.AcquireTokenInteractive(apiDefaultScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
CurrentAccount = authResult.Account;
// Retry API call
apiResult = await MockApiCall(result.AccessToken);
}
Recommandations et comportement attendu
Dans l’idéal, vous créez un flux distinct qui effectue les actions suivantes :
- Guide les utilisateurs dans le processus de consentement
- Provisionne votre application et votre API dans leur tenant ou compte Microsoft
- Achève le processus de consentement en une seule étape distincte de la connexion
Si vous ne séparez pas ce flux et que vous le combinez plutôt avec l’expérience de connexion de votre application, le processus peut devenir déroutant. Les utilisateurs peuvent rencontrer plusieurs invites de consentement. Pour améliorer l’expérience, envisagez d’ajouter un message dans votre application pour informer les utilisateurs qu’ils peuvent être invités à donner leur consentement plusieurs fois :
- Pour les comptes Microsoft, attendez-vous à au moins deux invites de consentement : une pour l’application cliente et une pour l’API.
- En règle générale, pour les comptes professionnels ou scolaires, une seule invite de consentement est requise.
Voici un exemple de code de bout en bout qui illustre une expérience utilisateur fluide. Ce code prend en charge tous les types de comptes et ne sollicite le consentement que si nécessaire.
string[] msGraphScopes = { "User.Read", "Mail.Send", "Calendar.Read" }
String[] apiScopes = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/access_as_user" };
String[] msGraphDefaultScope = { "https://graph.microsoft.com/.default" };
String[] apiDefaultScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
var accounts = await clientApp.GetAccountsAsync();
IAccount account = accounts.FirstOrDefault();
AuthenticationResult msGraphTokenResult = null;
AuthenticationResult apiTokenResult = null;
try
{
msGraphTokenResult = await clientApp.AcquireTokenSilent(msGraphScopes, account).ExecuteAsync();
apiTokenResult = await clientApp.AcquireTokenSilent(apiScopes, account).ExecuteAsync();
}
catch (Exception e1)
{
string catch1Message = e1.Message;
string catch2Message = String.Empty;
try
{
// First possible consent experience
var result = await clientApp.AcquireTokenInteractive(apiScopes)
.WithExtraScopesToConsent(msGraphScopes)
.WithAccount(account)
.ExecuteAsync();
CurrentAccount = result.Account;
msGraphTokenResult = await clientApp.AcquireTokenSilent(msGraphScopes, CurrentAccount).ExecuteAsync();
apiTokenResult = await clientApp.AcquireTokenSilent(apiScopes, CurrentAccount).ExecuteAsync();
}
catch(Exception e2)
{
catch2Message = e2.Message;
};
if(catch1Message.Contains("AADSTS650052") || catch2Message.Contains("AADSTS650052") || catch1Message.Contains("AADSTS70000") || catch2Message.Contains("AADSTS70000"))
{
// Second possible consent experience
var result = await clientApp.AcquireTokenInteractive(apiDefaultScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
CurrentAccount = result.Account;
msGraphTokenResult = await GetTokenAfterConsentAsync(msGraphScopes);
apiTokenResult = await GetTokenAfterConsentAsync(apiScopes);
}
}
// Call API
apiResult = await MockApiCall(apiTokenResult.AccessToken);
var contentMessage = await apiResult.Content.ReadAsStringAsync();
if(apiResult.StatusCode==HttpStatusCode.Forbidden)
{
var result = await clientApp.AcquireTokenInteractive(apiDefaultScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
CurrentAccount = result.Account;
// Retry API call
apiResult = await MockApiCall(result.AccessToken);
}
Contactez-nous pour obtenir de l’aide
Si vous avez des questions ou avez besoin d’aide, créez une demande de support ou demandez le support de la communauté Azure. Vous pouvez également soumettre des commentaires sur les produits à la communauté de commentaires Azure.