SOLVED
Home

Use Outlook REST API to send email failed when using App-only token.

%3CLINGO-SUB%20id%3D%22lingo-sub-40014%22%20slang%3D%22en-US%22%3EUse%20Outlook%20REST%20API%20to%20send%20email%20failed%20when%20using%20App-only%20token.%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-40014%22%20slang%3D%22en-US%22%3E%3CP%3EI%20am%26nbsp%3Btrying%20to%20use%20outlook%20REST%20API%20to%20send%20email%20by%20using%20app-only%20token%20and%20got%20the%20following%20exception.%3C%2FP%3E%3CP%3E%3CEM%3E%22%3CSTRONG%3Ex-ms-diagnostics%3A%202000008%3Breason%3D%22The%20token%20contains%20no%20permissions%2C%20or%20permissions%20can%20not%20be%20understood.%22%3Berror_category%3D%22invalid_grant%22%3C%2FSTRONG%3E%22%3C%2FEM%3E%3C%2FP%3E%3CP%3Ethe%20following%20steps%20is%20the%20one%20I%20did%3C%2FP%3E%3CP%3Ecreate%20a%20Azure%20AD%20app%20to%20update%26nbsp%3B%22keyCredentials%22%20section%20for%20certificate%20informaiton%26nbsp%3Bin%20manifest%20file.%3C%2FP%3E%3CP%3Econfigure%20%22O365%20exchange%20online%22%20to%20have%20%22send%20email%20as%20any%20user%22%20for%20%22Application%20Permissions%22%26nbsp%3B%3C%2FP%3E%3CP%3Erun%20the%20following%20code%3A%3C%2FP%3E%3CPRE%3E%20%20%20%20%20%20%20%20private%20async%20static%20Task%20SendEamil()%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20tenantId%20%3D%20%22yourtenant.onmicrosoft.com%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20clientId%20%3D%20%22your%20client%20id%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20resourceId%20%3D%20%22https%3A%2F%2Foutlook.office.com%2F%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20resourceUrl%20%3D%20%22https%3A%2F%2Foutlook.office.com%2Fapi%2Fv2.0%2Fusers%2Fservice%40contoso.com%2Fsendmail%22%3B%20%2F%2Fthis%20is%20your%20on-behalf%20user's%20UPN%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20authority%20%3D%20String.Format(%22https%3A%2F%2Flogin.windows.net%2F%7B0%7D%22%2C%20tenantId)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20certficatePath%20%3D%20%40%22c%3A%5Ctest.pfx%22%3B%20%2F%2Fthis%20is%20your%20certficate%20location.%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fstring%20certificatePassword%20%3D%20%22xxxx%22%3B%20%2F%2F%20this%20is%20your%20certificate%20password%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fread%20Azure%20Ad%20setting%20from%20a%20file.%20%0A%20%20%20%20%20%20%20%20%20%20%20%20string%20settingJson%20%3D%20String.Format(%22%7B0%7D%5C%5Csetting.settingjson%22%2C%20AppDomain.CurrentDomain.BaseDirectory)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20AzureAdSetting%20setting%20%3D%20AzureAdSetting.CreateInstance(settingJson)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20itemPayload%20%3D%20new%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Message%20%3D%20new%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Subject%20%3D%20%22Test%20email%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Body%20%3D%20new%20%7B%20ContentType%20%3D%20%22Text%22%2C%20Content%20%3D%20%22this%20is%20test%20email.%22%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ToRecipients%20%3D%20new%5B%5D%20%7B%20new%20%7B%20EmailAddress%20%3D%20new%20%7B%20Address%20%3D%20setting.SendEmail%20%7D%20%7D%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fif%20you%20need%20to%20load%20from%20certficate%20store%2C%20use%20different%20constructors.%20%0A%20%20%20%20%20%20%20%20%20%20%20%20X509Certificate2%20certificate%20%3D%20new%20X509Certificate2(setting.CertficatePath%2C%20setting.CertificatePassword%2C%20X509KeyStorageFlags.MachineKeySet)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20AuthenticationContext%20authenticationContext%20%3D%20new%20AuthenticationContext(setting.Authority%2C%20true)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20ClientAssertionCertificate%20cac%20%3D%20new%20ClientAssertionCertificate(setting.ClientId%2C%20certificate)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fget%20the%20access%20token%20to%20Outlook%20using%20the%20ClientAssertionCertificate%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20authenticationResult%20%3D%20await%20authenticationContext.AcquireTokenAsync(setting.ResourceId%2C%20cac)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20string%20token%20%3D%20authenticationResult.AccessToken%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Finitialize%20HttpClient%20for%20REST%20call%0A%20%20%20%20%20%20%20%20%20%20%20%20HttpClient%20client%20%3D%20new%20HttpClient()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20client.DefaultRequestHeaders.Add(%22Authorization%22%2C%20%22Bearer%20%22%20%2B%20token)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20client.DefaultRequestHeaders.Add(%22Accept%22%2C%20%22application%2Fjson%22)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Fsetup%20the%20client%20post%0A%20%20%20%20%20%20%20%20%20%20%20%20HttpContent%20content%20%3D%20new%20StringContent(JsonConvert.SerializeObject(itemPayload))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2FSpecify%20the%20content%20type.%20%0A%20%20%20%20%20%20%20%20%20%20%20%20content.Headers.ContentType%20%3D%20MediaTypeHeaderValue.Parse(%22application%2Fjson%3Bodata%3Dverbose%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20HttpResponseMessage%20result%20%3D%20await%20client.PostAsync(setting.ResourceUrl%2C%20content)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(result.IsSuccessStatusCode)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Femail%20send%20successfully.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Console.WriteLine(%22Email%20sent%20successfully.%20%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2Femail%20send%20failed.%20check%20the%20result%20for%20detail%20information%20from%20REST%20api.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Console.WriteLine(%22Email%20sent%20failed.%20Error%3A%20%7B0%7D%22%2C%20await%20result.Content.ReadAsStringAsync())%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%7D%3C%2FPRE%3E%3CP%3EThe%20weird%20thing%20I%20found%20out%20was%20that%20I%20tried%20above%20steps%20and%20code%20in%20my%20personal%20O365%20tenant%20and%20it%20works.%20Just%20wondering%20if%20anyone%20know%20any%20configures%20we%20need%20to%20turn%20on%20in%20O365%20central%20admin%20to%20allow%20Outlook%20REST%20API%20access%20via%20app-only%20token.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-LABS%20id%3D%22lingo-labs-40014%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3EAPI%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EDeveloper%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EExchange%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EOffice%20365%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E%3CLINGO-SUB%20id%3D%22lingo-sub-41109%22%20slang%3D%22en-US%22%3ERe%3A%20Use%20Outlook%20REST%20API%20to%20send%20email%20failed%20when%20using%20App-only%20token.%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-41109%22%20slang%3D%22en-US%22%3E%3CP%3EAh%2Cyes.%20For%20a%20single%20tenant%20application%2C%20the%20portal%20approach%20is%20fine.%20My%20approach%20works%20for%20multi-tenant.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EP.S.%20Those%20things%20in%20the%20portal%20are%20called%20a%20%22blade%22%20%26nbsp%3B%3A)%3C%2Fimg%3E%3C%2FP%3E%3CBLOCKQUOTE%3E%3CHR%20%2F%3E%3C%2FBLOCKQUOTE%3E%3C%2FLINGO-BODY%3E%3CLINGO-SUB%20id%3D%22lingo-sub-41040%22%20slang%3D%22en-US%22%3ERe%3A%20Use%20Outlook%20REST%20API%20to%20send%20email%20failed%20when%20using%20App-only%20token.%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-41040%22%20slang%3D%22en-US%22%3E%3CP%3EThanks%20for%20your%20note%2C%20Paul.%20I%20realized%20that%20the%20admin%20consent%20is%20required%20for%26nbsp%3Bthe%20Azure%20Ad%20App%20created%20by%20non-admin%20like%20regular%20develoers.%20I%20also%20found%20out%20that%20there%20is%20a%20button%20call%20%22Grant%20Permission%22%20right%20beside%20%22add%22%20button%20at%20%22Application%20Permission%22%20slab%20in%20new%20Azure%20AD%20preivew%20portal.%20When%20admin%20click%20that%20button%2C%20it%20actually%20consent%20the%20permission.%20I%20wasn't%20able%20to%20find%20the%20detail%20document%20to%20explain%20what%20that%20button%20means%20from%20Azure%20AD%20portal.%26nbsp%3Bthis%20button%26nbsp%3Bactually%20solved%20my%20issues.%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-SUB%20id%3D%22lingo-sub-41024%22%20slang%3D%22en-US%22%3ERe%3A%20Use%20Outlook%20REST%20API%20to%20send%20email%20failed%20when%20using%20App-only%20token.%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-41024%22%20slang%3D%22en-US%22%3E%3CP%3EMail.Send%20in%20App-only%20requires%20Admin%20Consent.%20(%3CA%20href%3D%22https%3A%2F%2Fgraph.microsoft.io%2Fen-us%2Fdocs%2Fauthorization%2Fpermission_scopes%22%20target%3D%22_blank%22%20rel%3D%22nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Fgraph.microsoft.io%2Fen-us%2Fdocs%2Fauthorization%2Fpermission_scopes%3C%2FA%3E)%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EYou%20can%20request%20admin%20consent%20by%20pasking%26nbsp%3B%26amp%3Bprompt%3Dadmin_consent%20to%20the%20authorize%20endpoint.%3C%2FP%3E%3C%2FLINGO-BODY%3E
Frank Chen
Occasional Contributor

I am trying to use outlook REST API to send email by using app-only token and got the following exception.

"x-ms-diagnostics: 2000008;reason="The token contains no permissions, or permissions can not be understood.";error_category="invalid_grant""

the following steps is the one I did

create a Azure AD app to update "keyCredentials" section for certificate informaiton in manifest file.

configure "O365 exchange online" to have "send email as any user" for "Application Permissions" 

run the following code:

        private async static Task SendEamil()
        {
            //string tenantId = "yourtenant.onmicrosoft.com";
            //string clientId = "your client id";
            //string resourceId = "https://outlook.office.com/";
            //string resourceUrl = "https://outlook.office.com/api/v2.0/users/service@contoso.com/sendmail"; //this is your on-behalf user's UPN
            //string authority = String.Format("https://login.windows.net/{0}", tenantId);
            //string certficatePath = @"c:\test.pfx"; //this is your certficate location.
            //string certificatePassword = "xxxx"; // this is your certificate password
            //read Azure Ad setting from a file. 
            string settingJson = String.Format("{0}\\setting.settingjson", AppDomain.CurrentDomain.BaseDirectory);
            AzureAdSetting setting = AzureAdSetting.CreateInstance(settingJson);

            var itemPayload = new
            {
                Message = new
                {
                    Subject = "Test email",
                    Body = new { ContentType = "Text", Content = "this is test email." },
                    ToRecipients = new[] { new { EmailAddress = new { Address = setting.SendEmail } } }
                }
            };

            //if you need to load from certficate store, use different constructors. 
            X509Certificate2 certificate = new X509Certificate2(setting.CertficatePath, setting.CertificatePassword, X509KeyStorageFlags.MachineKeySet);
            AuthenticationContext authenticationContext = new AuthenticationContext(setting.Authority, true);
            ClientAssertionCertificate cac = new ClientAssertionCertificate(setting.ClientId, certificate);

            //get the access token to Outlook using the ClientAssertionCertificate
            var authenticationResult = await authenticationContext.AcquireTokenAsync(setting.ResourceId, cac);
            string token = authenticationResult.AccessToken;

            //initialize HttpClient for REST call
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
            client.DefaultRequestHeaders.Add("Accept", "application/json");

            //setup the client post
            HttpContent content = new StringContent(JsonConvert.SerializeObject(itemPayload));
            //Specify the content type. 
            content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");
            HttpResponseMessage result = await client.PostAsync(setting.ResourceUrl, content);
            if (result.IsSuccessStatusCode)
            {
                //email send successfully.
                Console.WriteLine("Email sent successfully. ");
            }
            else
            {
                //email send failed. check the result for detail information from REST api.
                Console.WriteLine("Email sent failed. Error: {0}", await result.Content.ReadAsStringAsync());
            }

        }

The weird thing I found out was that I tried above steps and code in my personal O365 tenant and it works. Just wondering if anyone know any configures we need to turn on in O365 central admin to allow Outlook REST API access via app-only token.

 

3 Replies
Solution

Mail.Send in App-only requires Admin Consent. (https://graph.microsoft.io/en-us/docs/authorization/permission_scopes)

 

You can request admin consent by pasking &prompt=admin_consent to the authorize endpoint.

Thanks for your note, Paul. I realized that the admin consent is required for the Azure Ad App created by non-admin like regular develoers. I also found out that there is a button call "Grant Permission" right beside "add" button at "Application Permission" slab in new Azure AD preivew portal. When admin click that button, it actually consent the permission. I wasn't able to find the detail document to explain what that button means from Azure AD portal. this button actually solved my issues.

Ah,yes. For a single tenant application, the portal approach is fine. My approach works for multi-tenant.

 

P.S. Those things in the portal are called a "blade"  :)


Related Conversations
Extentions Synchronization
Deleted in Discussions on
3 Replies
Tabs and Dark Mode
cjc2112 in Discussions on
36 Replies
flashing a white screen while open new tab
Deleted in Discussions on
14 Replies
Stable version of Edge insider browser
HotCakeX in Discussions on
35 Replies