Search code examples
azuresmtp

Scope not valid error when using ConfidentialClientApplicationBuilder


I am using ConfidentialClientApplicationBuilder to get token and then send email

var pca = ConfidentialClientApplicationBuilder
                     .Create(ClientId)
                     .WithClientSecret(clientSecret)
                     .WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
                     .WithRedirectUri(RedirectUrl)
                     .Build();




            var outlookScope = new string[] { "https://outlook.office365.com/SMTP.Send" };   // for graph use "https://graph.microsoft.com/.default"

            AuthenticationResult result = null;

            try
            {
                result = await pca.AcquireTokenForClient(outlookScope)
                    .ExecuteAsync();
            }
            catch (MsalException ex)
            {
                Console.WriteLine($"Error acquiring access token: {ex}");
            }

But i get this error when on result Microsoft.Identity.Client.MsalServiceException: 'AADSTS1002012: The provided value for scope https://outlook.office365.com/SMTP.Send is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).

What scope should i use to get token successfully, further i am using this smpt to send email

using (var emailClient = new MailKit.Net.Smtp.SmtpClient())
                {
                    var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
                    await emailClient.ConnectAsync(SMPTServerName, SMPTServerPort, SecureSocketOptions.StartTls);  //google smtp.gmail.com
                    await emailClient.AuthenticateAsync(oauth2);

// Message Body 

await emailClient.SendAsync(message);

Update Thanks to Rukmini, i was to able to get access token using

var outlookScope = new string[] { "https://outlook.office365.com/.default" };

Now the issue that i am having is to authenticate that token . This is the code

  

    if (result != null)
                {
                    Console.WriteLine($"Access token: {result.AccessToken}");
    
                    var message = new MimeMessage();
                    message.From.Add(new MailboxAddress("Sender Name", "sender@example.com"));
                    message.To.Add(new MailboxAddress("Recipient Name", "recipient@example.com"));
                    message.Subject = "Test Email";
                    message.Body = new TextPart("plain")
                    {
                        Text = "This is a test email sent using MailKit and Microsoft.Identity.Client."
                    };
    
                    using (var emailClient = new SmtpClient())
                    {
                        var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
                        await emailClient.ConnectAsync("smtp.office365.com", 587, SecureSocketOptions.StartTls);
                        await emailClient.AuthenticateAsync(oauth2);
                        await emailClient.SendAsync(message);
                        await emailClient.DisconnectAsync(true);
                    }
                }

Now firstly result Account is null, so I get this error enter image description here

Even if i put a random username, get this error MailKit.Security.AuthenticationException: '535: 5.7.3 Authentication unsuccessful [DX0P273CA0070.AREP273.PROD.OUTLOOK.COM 2023-10-16T04:50:43.626Z 08DBCDB69CE3A094]' enter image description here


Solution

  • I tried the same code in my environment and got the same error as below:

    enter image description here

    enter image description here

    Note that: If you are using client credential flow to generate the access token, then the scope must have /.default as suffix to the resource. In your case the scope must be https://outlook.office365.com/.default

    I modified the code like below to fetch the access token for outlook resource:

    using Microsoft.Identity.Client;
    using System;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static async System.Threading.Tasks.Task Main(string[] args)
            {
                string ClientId = "ClientID";
                string clientSecret = "ClientSecret";
                string Tenant = "TenantID";
                string RedirectUrl = "https://jwt.ms";
    
                var pca = ConfidentialClientApplicationBuilder
                    .Create(ClientId)
                    .WithClientSecret(clientSecret)
                    .WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
                    .WithRedirectUri(RedirectUrl)
                    .Build();
    
                var outlookScope = new string[] { "https://outlook.office365.com/.default" };
    
                AuthenticationResult result = null;
    
                try
                {
                    result = await pca.AcquireTokenForClient(outlookScope)
                        .ExecuteAsync();
                }
                catch (MsalException ex)
                {
                    Console.WriteLine($"Error acquiring access token: {ex}");
                }
    
                if (result != null)
                {
                    Console.WriteLine($"Access token: {result.AccessToken}");
                }
            }
        }
    }
    

    enter image description here

    Decoded token:

    enter image description here

    UPDATE:

    You can make use of Microsoft Graph API to send mail like below:

    Grant API permissions:

    enter image description here

    Use the below code:

    using Azure.Identity;
    using Microsoft.Graph;
    using Microsoft.Graph.Models;
    using Microsoft.Graph.Models.ODataErrors;
    
    var scopes = new[] { "https://graph.microsoft.com/.default" };
    
    var clientId = "XXX";
    var tenantId = "XXX";
    var clientSecret = "***";
    
    var options = new ClientSecretCredentialOptions
    {
        AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
    };
    
    var clientSecretCredential = new ClientSecretCredential(
        tenantId, clientId, clientSecret, options);
    
    var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
    
    var requestBody = new Microsoft.Graph.Users.Item.SendMail.SendMailPostRequestBody
    {
        Message = new Message
        {
            Subject = "Test mail",
            Body = new ItemBody
            {
                ContentType = BodyType.Html,
                Content = "This is Test mail",
            },
            ToRecipients = new List<Recipient>
            {
                new Recipient
                {
                    EmailAddress = new EmailAddress
                    {
                        Address = "user@***.onmicrosoft.com",
                    },
                },
            },
        },
    };
    try
    {
        await graphClient.Users["user1@**.onmicrosoft.com"].SendMail.PostAsync(requestBody);
        Console.WriteLine("Successfully sent mail");
    }
    catch (ODataError odataError)
    {
        Console.WriteLine(odataError.Error.Code);
        Console.WriteLine(odataError.Error.Message);
    }
    

    enter image description here