Microsoft API Graph - send mail on behalf of a user

Alejandro Manuel Pinto Rodríguez 26 Reputation points
2021-03-17T11:18:12.28+00:00

Hello.

I have a .Net Core API that needs to send notification mails.

To do this, an application with delegated permissions and a user for sending mail has been created in Azure AD.
The application has been granted mail.send permission for the graph api.

I have installed the Microsoft.Graph library version 3.21.0 in my API and I have created the following function to send notifications.

private static void SendMailMessageService(IConfiguration configuration, string template, string user)
{
 Microsoft.Identity.Client.IConfidentialClientApplication clientApplication = 
 Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
 .Create(configuration["AzureMail:ClientId"])
 .WithTenantId(configuration["AzureMail:TenantId"])
 .WithClientSecret(configuration["AzureMail:ClientSecret"])
 .Build();

 Microsoft.Graph.Auth.OnBehalfOfProvider authProvider =
 new Microsoft.Graph.Auth.OnBehalfOfProvider(clientApplication);

 Microsoft.Graph.GraphServiceClient graphClient =
 new Microsoft.Graph.GraphServiceClient(authProvider);

 Microsoft.Graph.Message msg = new Microsoft.Graph.Message()
 {
 Subject = configuration["AzureMail:Subject"].ToString(),
 Body = new Microsoft.Graph.ItemBody()
 {
 Content = template,
 ContentType = Microsoft.Graph.BodyType.Html
 },
 ToRecipients = new List<Microsoft.Graph.Recipient>() {
 new Microsoft.Graph.Recipient {
 EmailAddress = new Microsoft.Graph.EmailAddress {
 Address = user
 }
 }
 }
 };

 var request = graphClient.Users[configuration["AzureMail:UserFrom"]].SendMail(msg, false).Request();

 var result = request.PostAsync();
 result.Wait();
}

When I run this function it is returning the following error:

{Status Code: 0
Microsoft.Graph.ServiceException: Code: generalException
Message: An error occurred sending the request.
 ---> Microsoft.Graph.Auth.AuthenticationException: Code: generalException
Message: Unexpected exception occurred while authenticating the request.
 ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.GetBodyParameters()
   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 Microsoft.Graph.Auth.OnBehalfOfProvider.GetNewAccessTokenAsync(AuthenticationProviderOption msalAuthProviderOption)
   --- End of inner exception stack trace ---
   at Microsoft.Graph.Auth.OnBehalfOfProvider.GetNewAccessTokenAsync(AuthenticationProviderOption msalAuthProviderOption)
   at Microsoft.Graph.Auth.OnBehalfOfProvider.AuthenticateRequestAsync(HttpRequestMessage httpRequestMessage)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Microsoft.Graph.BaseRequest.SendRequestAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)
   at Microsoft.Graph.BaseRequest.SendAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)}

 

The idea is to send the notification email from the account indicated in configuration ["AzureMail: UserFrom"].
Could you help me? Do you know why this is giving me an error? Is it a problem with my code? Could it be influencing that this user has the MFA active?
I need to solve it as soon as possible since in a few days it will be the only way in which we can send notifications.

I hope your answer.
A greeting and thanks to all.

Microsoft Security | Microsoft Graph
0 comments No comments
{count} votes

Accepted answer
  1. Diana Wanjuhi 1,376 Reputation points
    2021-03-19T05:39:18.75+00:00

    Hello @Alejandro Manuel Pinto Rodríguez this error likely happens because Security Defaults is enabled on the tenant. One of the policies enforced is MFA where users can't sign in without registering for Azure MFA. Please see documentation here on disabling security defaults ( although not recommended)

    I hope this helps,

    Diana.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Alejandro Manuel Pinto Rodríguez 26 Reputation points
    2021-03-17T13:01:44.983+00:00

    I have done new tests, changing the first part of the function for this:

    Microsoft.Identity.Client.IPublicClientApplication publicClientApplication =
        Microsoft.Identity.Client.PublicClientApplicationBuilder
            .Create(configuration["AzureMail:ClientId"])
            .WithTenantId(configuration["AzureMail:TenantId"])
            .Build();
    
    var str = configuration["AzureMail:Key"];
    var pwd = new System.Security.SecureString();
    foreach (char c in str) pwd.AppendChar(c);
    
    string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
    publicClientApplication.AcquireTokenByUsernamePassword(
        scopes, configuration["AzureMail:UserFrom"], pwd
    ).ExecuteAsync().Wait();
    
    Microsoft.Graph.Auth.UsernamePasswordProvider authProvider =
        new Microsoft.Graph.Auth.UsernamePasswordProvider(publicClientApplication, scopes);
    

    In this case I have a new error:

    {MSAL.NetCore.4.24.0.0.MsalUiRequiredException: 
        ErrorCode: invalid_grant
    Microsoft.Identity.Client.MsalUiRequiredException: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000003-0000-0000-c000-000000000000'.
    Trace ID: d702b365-ea1e-4037-a07c-1c863e1b4d00
    Correlation ID: 2ee96303-9dfe-4d8c-a9bd-5abaf97faa07
    Timestamp: 2021-03-17 12:57:36Z
       at Microsoft.Identity.Client.OAuth2.OAuth2Client.ThrowServerException(HttpResponse response, RequestContext requestContext)
       at Microsoft.Identity.Client.OAuth2.OAuth2Client.CreateResponse[T](HttpResponse response, RequestContext requestContext)
       at Microsoft.Identity.Client.OAuth2.OAuth2Client.ExecuteRequestAsync[T](Uri endPoint, HttpMethod method, RequestContext requestContext, Boolean expectErrorsOn200OK, Boolean addCommonHeaders)
       at Microsoft.Identity.Client.OAuth2.OAuth2Client.GetTokenAsync(Uri endPoint, RequestContext requestContext, Boolean addCommonHeaders)
       at Microsoft.Identity.Client.OAuth2.TokenClient.SendHttpAndClearTelemetryAsync(String tokenEndpoint)
       at Microsoft.Identity.Client.OAuth2.TokenClient.SendHttpAndClearTelemetryAsync(String tokenEndpoint)
       at Microsoft.Identity.Client.OAuth2.TokenClient.SendTokenRequestAsync(IDictionary`2 additionalBodyParameters, String scopeOverride, String tokenEndpointOverride, CancellationToken cancellationToken)
       at Microsoft.Identity.Client.Internal.Requests.RequestBase.SendTokenRequestAsync(String tokenEndpoint, IDictionary`2 additionalBodyParameters, CancellationToken cancellationToken)
       at Microsoft.Identity.Client.Internal.Requests.UsernamePasswordRequest.ExecuteAsync(CancellationToken cancellationToken)
       at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
    

    Can somebody help me?

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.