I'm faced with the following situation: I have two app registrations in Azure AD - an Angular 17 web app and a REST API with .NET 6. I've set up authentication/authorization through Azure AD. The Angular app calls the API with an access token through MSAL, and everything works as intended.
Recently, I extended my API with a Graph client to draft an Outlook email and return a link that Angular can use to open Outlook and display the email.
The user can then review, manually adjust, and send the email. For this, I extended my API app registration in Azure with the "Mail.ReadWrite" scope and implemented it in C# as follows:
string clientId = _configuration["AzureAd:ClientId"];
string clientSecret = _configuration["AzureAd:ClientSecret"];
string tenantId = _configuration["AzureAd:TenantId"];
string[] scopes = { "Mail.ReadWrite" };
var options = new OnBehalfOfCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud };
var onBehalfOfCredential = new OnBehalfOfCredential(tenantId, clientId, clientSecret, userAccessToken, options);
var graphClient = new GraphServiceClient(onBehalfOfCredential, scopes);
var requestBody = new Message
{
Subject = $"Test",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "Sehr geehrte Damen und Herren,<br/><br/>" +
"Test"
},
ToRecipients = new List<Recipient>
{
new() { EmailAddress = new EmailAddress { Address = sendDocumentsRequest.Receiver } }
},
From = new Recipient { EmailAddress = new EmailAddress { Address = sendDocumentsRequest.Sender } }
};
// 1. Step: Create E-Mail in drafts folder
var resultMessage = await graphClient.Me.Messages.PostAsync(requestBody);
and this works perfectly for me.
However, all other users receive the following error message:
2024-03-20 08:28:50.578 +01:00 [ERR] Error while processing the documents in Session ID XXXXX Azure.Identity.AuthenticationFailedException: OnBehalfOfCredential authentication failed: AADSTS65001: The user or administrator has not consented to use the application with ID 'XXXXXX' named 'API_DEV'. Send an interactive authorization request for this user and resource. ---> MSAL.NetCore.4.59.0.0.MsalUiRequiredException: ErrorCode: invalid_grant Microsoft.Identity.Client.MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID 'XXXX' named 'XXXX'. Send an interactive authorization request for this user and resource. Microsoft.Identity.Client.Internal.Requests.RequestBase.HandleTokenRefreshErrorAsync(MsalServiceException e, MsalAccessTokenCacheItem cachedAccessTokenItem) at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.ApiConfig.Executors.ConfidentialClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenOnBehalfOfParameters onBehalfOfParameters, CancellationToken cancellationToken) at Azure.Identity.AbstractAcquireTokenParameterBuilderExtensions.ExecuteAsync[T](AbstractAcquireTokenParameterBuilder`1 builder, Boolean async, CancellationToken cancellationToken) at Azure.Identity.MsalConfidentialClient.AcquireTokenOnBehalfOfCoreAsync(String[] scopes, String tenantId, UserAssertion userAssertionValue, Boolean enableCae, Boolean async, CancellationToken cancellationToken) at Azure.Identity.MsalConfidentialClient.AcquireTokenOnBehalfOfAsync(String[] scopes, String tenantId, UserAssertion userAssertionValue, Boolean enableCae, Boolean async, CancellationToken cancellationToken) at Azure.Identity.OnBehalfOfCredential.GetTokenInternalAsync(TokenRequestContext requestContext, Boolean async, CancellationToken cancellationToken) StatusCode: 400
They don't get a consent prompt. I'm unsure if I already confirmed this consent in a unit test. The users who use the application are assigned to groups associated with the application. It appears the issue is with MSAL.NetCore.4.59.0.0.MsalUiRequiredException. How could I program the call differently? How should I address this issue?
=> AdminConsentRequired = no => Microsoft documentation learn.microsoft.com/de-de/graph/permissions-reference
The error usually occurs if you missed granting consent to the added permission in either API or Client applications.
Initially, I too got same error when I ran same code by passing token generated without granting consent like this:
To resolve the error, make sure to grant consent to the added permissions either in
App registration
or inEnterprise application
where admin will review the permissions and grant accordingly.
In my case, I granted admin consent to added permission in API app like this:
When I ran below code in my environment after granting consent, I got response like this:
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Me.SendMail;
using Microsoft.Graph.Models;
namespace GraphSendMail
{
class Program
{
static async Task Main(string[] args)
{
try
{
string clientId = "appId";
string clientSecret = "secret";
string tenantId = "tenantId";
string userAccessToken = "token"; // Provide user access token here
string[] scopes = { "Mail.ReadWrite" };
var options = new OnBehalfOfCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud };
var onBehalfOfCredential = new OnBehalfOfCredential(tenantId, clientId, clientSecret, userAccessToken, options);
var graphClient = new GraphServiceClient(onBehalfOfCredential, scopes);
var requestBody = new SendMailPostRequestBody
{
Message = new Message
{
Subject = "Test",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "Sehr geehrte Damen und Herren,<br/><br/>" +
"Test"
},
ToRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "[email protected]" // Provide recipient email here
}
}
},
CcRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "[email protected]" // Provide cc email here
}
}
}
},
SaveToSentItems = true
};
await graphClient.Me.SendMail.PostAsync(requestBody);
Console.WriteLine("Email successfully sent.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}
}
Response:
To confirm that, I checked the same in user's Outlook where mail sent successfully like this:
If you prefer Administrator to authorize all users by reviewing the permissions, you can check it in Enterprise application
like this: