Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel wird erläutert, wie Sie die gebündelte Zustimmung für Microsoft Entra ID-Anwendungen konfigurieren.
Symptome
Sie verfügen über eine benutzerdefinierte Client-App und eine benutzerdefinierte API-App, und Sie erstellen App-Registrierungen für beide Apps in Microsoft Entra ID. Sie konfigurieren die gebündelte Zustimmung für beide Apps. In diesem Szenario erhalten Sie möglicherweise eine der folgenden Fehlermeldungen, wenn Sie versuchen, sich bei einer der beiden Apps anzumelden:
AADSTS70000: Die Anforderung wurde verweigert, da mindestens ein angeforderter Bereich nicht autorisiert oder abgelaufen ist. Der Benutzer muss sich zuerst anmelden und der Clientanwendung Zugriff auf den angeforderten Bereich gewähren.
AADSTS650052: Die App versucht, auf einen Dienst\"{app_id}\"(\"app_name\") zuzugreifen, für den Ihre Organisation %\"{organization}\" keinen Dienstprinzipal hat. Wenden Sie sich an Ihren IT-Administrator, um die Konfiguration Ihrer Service-Abonnements zu überprüfen oder die Zustimmung zur Anwendung zu geben, um den erforderlichen Dienstprinzipal zu erstellen.
Lösung
Schritt 1: Konfigurieren von knownClientApplications für die API-App-Registrierung
Fügen Sie die benutzerdefinierte Client-App-ID zur Eigenschaft der benutzerdefinierten API-App-Registrierung knownClientApplications
hinzu. Weitere Informationen finden Sie unter knownClientApplications-Attribut.
Schritt 2: Konfigurieren von API-Berechtigungen
Stellen Sie Folgendes sicher:
- Alle erforderlichen API-Berechtigungen sind sowohl für die benutzerdefinierten Client- als auch für benutzerdefinierte API-App-Registrierungen ordnungsgemäß konfiguriert.
- Die registrierung benutzerdefinierter Client-Apps enthält die API-Berechtigungen, die in der benutzerdefinierten API-App-Registrierung definiert sind.
Schritt 3: Die Anmeldeanforderung
Ihre Authentifizierungsanforderung muss den .default
Bereich für Microsoft Graph verwenden. Für Microsoft-Konten muss der Bereich für die benutzerdefinierte API sein.
Beispielanforderung für Microsoft-Konten sowie Geschäfts- oder Schulkonten
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
Hinweis
Dem Client scheint die Berechtigung für die API zu fehlen. Diese Bedingung wird erwartet, da der Client als knownClientApplication
aufgeführt ist.
Beispielanforderung nur für Arbeits- oder Schulkonten
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
Implementierung mithilfe von MSAL.NET
String[] consentScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
var loginResult = await clientApp.AcquireTokenInteractive(consentScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
Die Weitergabe der Zustimmung für neue Dienstprinzipale und Berechtigungen kann einige Zeit in Anspruch nehmen. Ihre Anwendung sollte diese Verzögerung erfolgreich behandeln.
Abrufen von Token für mehrere Ressourcen
Wenn Ihre Client-App Token für eine andere Ressource, z. B. Microsoft Graph, erwerben muss, müssen Sie Logik implementieren, um potenzielle Verzögerungen zu verarbeiten, nachdem Benutzer der Anwendung zugestimmt haben. Hier sind einige Empfehlungen dafür:
- Verwenden Sie den
.default
Bereich, wenn Sie Token anfordern. - Verfolgen Sie erlangte Geltungsbereiche, bis der erforderliche Geltungsbereich zurückgegeben wird.
- Fügen Sie eine Verzögerung hinzu, wenn das Ergebnis weiterhin nicht den erforderlichen Umfang erreicht.
Derzeit, wenn AcquireTokenSilent
fehlschlägt, fordert MSAL eine erfolgreiche interaktive Authentifizierung, bevor es einen weiteren stillen Token-Erwerb gestattet. Diese Einschränkung gilt auch, wenn ein gültiges Aktualisierungstoken verfügbar ist.
Hier ist ein Beispielcode, der die Wiederholungslogik verwendet:
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;
}
Informationen zur benutzerdefinierten API, die den Fluss "Im Auftrag von" verwendet
Ähnlich wie bei der Client-App kann Ihre benutzerdefinierte API möglicherweise unmittelbar nach der Zustimmung fehlschlagen, wenn sie versucht, Token für eine andere Ressource mithilfe des On-Behalf-Of-Flusses (OBO) abzurufen. Um dieses Problem zu beheben, können Sie wiederholungslogik und Bereichsnachverfolgung implementieren, wie im folgenden Beispielcode gezeigt:
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");
Wenn alle Wiederholungen fehlschlagen, geben Sie eine Fehlermeldung zurück, und weisen Sie den Client an, einen vollständigen Zustimmungsprozess zu starten.
Beispiel für Clientcode, der davon ausgeht, dass Ihre API eine 403 auslöst
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);
}
Empfehlungen und erwartetes Verhalten
Im Idealfall erstellen Sie einen separaten Fluss, der die folgenden Aktionen ausführt:
- Führt Benutzer durch den Zustimmungsprozess
- Stellt Ihre App und API in ihrem Mandanten- oder Microsoft-Konto bereit
- Schließt die Zustimmung in einem einzigen Schritt ab, der von der Anmeldung getrennt ist
Wenn Sie diesen Fluss nicht trennen und ihn stattdessen mit der Anmeldeoberfläche Ihrer App kombinieren, kann der Prozess verwirrend werden. Benutzer können auf mehrere Zustimmungsaufforderungen stoßen. Um die Benutzererfahrung zu verbessern, sollten Sie eine Nachricht in Ihrer App hinzufügen, um Benutzer darüber zu informieren, dass sie möglicherweise aufgefordert werden, mehr als einmal zuzustimmen:
- Für Microsoft-Konten erwarten Sie mindestens zwei Zustimmungsaufforderungen: eine für die Client-App und eine für die API.
- Für Arbeits-, Schul- oder Universitätskonten ist in der Regel nur eine einzige Zustimmungsaufforderung erforderlich.
Es folgt ein End-to-End-Codebeispiel, das eine reibungslose Benutzererfahrung veranschaulicht. Dieser Code unterstützt alle Kontotypen und Aufforderungen zur Zustimmung nur bei Bedarf.
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);
}
Kontaktieren Sie uns für Hilfe
Wenn Sie Fragen haben oder Hilfe benötigen, erstellen Sie eine Support-Anfrage oder wenden Sie sich an den Azure Community-Support. Sie können auch Produktfeedback an die Azure Feedback Community senden.