Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo discute como configurar o consentimento empacotado para aplicativos de ID do Microsoft Entra.
Sintomas
Você tem um aplicativo cliente personalizado e um aplicativo de API personalizado e cria registros de aplicativo para ambos os aplicativos na ID do Microsoft Entra. Você configura o consentimento empacotado para ambos os aplicativos. Nesse cenário, você pode receber uma das seguintes mensagens de erro ao tentar entrar em qualquer aplicativo:
AADSTS70000: a solicitação foi negada porque um ou mais escopos solicitados não são autorizados ou expiraram. O usuário deve primeiro entrar e conceder ao aplicativo cliente acesso ao escopo solicitado.
AADSTS650052: O aplicativo está tentando acessar um serviço\"{app_id}\"(\"app_name\") para o qual sua organização %\"{organization}\" não tem uma entidade de serviço. Entre em contato com o administrador de TI para examinar a configuração de suas assinaturas de serviço ou consentir com o aplicativo para criar a entidade de serviço necessária.
Solução
Etapa 1: configurar knownClientApplications para o registro do aplicativo de API
Adicione a ID personalizada do aplicativo cliente à propriedade do registro do aplicativo de API personalizado de knownClientApplications
. Para obter mais informações, consulte o atributo knownClientApplications.
Etapa 2: Configurar permissões de API
Certifique-se de que:
- Todas as permissões de API necessárias são configuradas corretamente nos registros personalizados do cliente e do aplicativo de API personalizado.
- O registro de aplicativo cliente personalizado inclui as permissões de API definidas no registro de aplicativo de API personalizado.
Etapa 3: A solicitação de entrada
Sua solicitação de autenticação deve usar o escopo .default
do Microsoft Graph. Para contas da Microsoft, o escopo deve ser para a API personalizada.
Solicitação de exemplo para contas da Microsoft e contas corporativas ou de estudante
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
Observação
O cliente parece não ter permissão para a API. Essa condição é esperada porque o cliente está listado como knownClientApplication
.
Solicitação de exemplo somente para contas corporativas ou de estudante
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
Implementação usando MSAL.NET
String[] consentScope = { "api://ae5a0bbe-d6b3-4a20-867b-c8d9fd442160/.default" };
var loginResult = await clientApp.AcquireTokenInteractive(consentScope)
.WithAccount(account)
.WithPrompt(Prompt.Consent)
.ExecuteAsync();
A propagação de consentimento para novas entidades de serviço e permissões pode levar algum tempo para ser concluída. Seu aplicativo deve lidar com esse atraso com êxito.
Adquirir tokens para vários recursos
Se o aplicativo cliente precisar adquirir tokens para outro recurso, como o Microsoft Graph, você deverá implementar a lógica para lidar com possíveis atrasos após o consentimento dos usuários para o aplicativo. Veja algumas recomendações:
- Use o
.default
escopo ao pedir tokens. - Acompanhe os escopos adquiridos até que o necessário seja retornado.
- Adicione um atraso se o resultado ainda não tiver o escopo necessário.
Atualmente, se AcquireTokenSilent
falhar, a MSAL exigirá uma autenticação interativa bem-sucedida antes de permitir outra aquisição de token silenciosa. Essa restrição se aplica mesmo se um token de atualização válido estiver disponível.
Aqui está um exemplo de código que usa a lógica de repetição:
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;
}
Sobre a API personalizada que usa o fluxo em nome de outrem
Semelhante ao aplicativo cliente, quando sua API personalizada tenta adquirir tokens para outro recurso usando o fluxo OBO (On-Behalf-Of), ele pode falhar imediatamente após o consentimento. Para resolver esse problema, você pode implementar a lógica de repetição e o controle de escopo, como no seguinte código de exemplo:
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");
Se todas as novas tentativas falharem, retorne uma mensagem de erro e instrua o cliente a iniciar um processo de consentimento completo.
Exemplo de código do cliente que pressupõe que sua API gera um 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);
}
Recomendações e comportamento esperado
O ideal é criar um fluxo separado que execute as seguintes ações:
- Orienta os usuários por meio do processo de consentimento
- Provisiona seu aplicativo e API em seu locatário ou conta da Microsoft
- Conclui o consentimento em uma única etapa, separada do login.
Se você não separar esse fluxo e combiná-lo com a experiência de entrada do aplicativo, o processo poderá se tornar confuso. Os usuários podem encontrar vários prompts de consentimento. Para melhorar a experiência, considere adicionar uma mensagem em seu aplicativo para informar aos usuários que eles podem ser solicitados a consentir mais de uma vez:
- Para contas da Microsoft, espere pelo menos dois prompts de consentimento: um para o aplicativo cliente e outro para a API.
- Normalmente, para contas corporativas ou de estudante, apenas um prompt de consentimento é necessário.
Veja a seguir um exemplo de código de ponta a ponta que demonstra uma experiência suave do usuário. Esse código dá suporte a todos os tipos de conta e solicita consentimento somente quando necessário.
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);
}
Entre em contato conosco para obter ajuda
Se você tiver dúvidas ou precisar de ajuda, crie uma solicitação de suporte ou peça ajuda à comunidade de suporte do Azure. Você também pode enviar comentários sobre o produto para a comunidade de comentários do Azure.