Forum Discussion

Deleted's avatar
Deleted
Nov 14, 2018
Solved

Unauthorized attempting to send Email with valid access token

Hi,

 

I'm attempting to programatically send email using PowerShell and the Office 365 outlook REST API (can't use SMTP as it's blocked).

 

I've managed to set up my Web App in Azure AD with what I'm pretty sure are the requisite permissions and have authorized those via the adminconsent URI. I've set up the shared secret and can successfully retrieve an access token using the client credentials flow, but when I attempt to use that token to send mail I get a 401 unauthorized error.

 

Here's my code with all the juicy bits replaced

 

$emailaddress = "<email address to send to>"
$secret = "<secret from Azure AD App registration>"
$urlencodedsecret = [System.Web.HttpUtility]::UrlEncode($secret)
$tenantid = "<our tenant id>"
$appid="<my app id>"
$requesturi = "https://login.microsoftonline.com/$tenantid/oauth2/token"
$body = "grant_type=client_credentials&client_id=$appid&client_secret=$urlencodedsecret" 
$response = Invoke-RestMethod -Method POST -Uri $requesturi -body $body -ContentType "application/x-www-form-urlencoded"
$token = $response.access_token;
 
So that all works OK and I get a nice shiny new access token - however when I try to use it ...
 
$bearerAuthHeader = "Bearer {0}" -f $token;
$headers = @{
    Authorization = $bearerAuthHeader;
    "x-AnchorMailbox" = "<my email address>"
};
$uri = "https://outlook.office365.com/api/v2.0/me/sendmail";
$body = '{
  "Message": {
    "Subject": "<Subject>",
    "Body": {
      "ContentType": "Text",
      "Content": "<Content>"
    },
    "ToRecipients": [
      {
        "EmailAddress": {
          "Address": "' + $emailaddress + '"
        }
      }
    ],
    "Attachments": [
      {
        "@odata.type": "#Microsoft.OutlookServices.FileAttachment",
        "Name": "<attachment name>,
        "ContentBytes": "<Base 64 encoded attachment>"
      }
    ]
  },
  "SaveToSentItems": "false"
}'
$sendMsgResult = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Headers $headers -ContentType "application/json";
 
 
And that results in the following error
Invoke-RestMethod : The remote server returned an error: (401) Unauthorized.
At line:43 char:18
+ ... MsgResult = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Hea ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
 
I've successfully used the exact same email sending code code with an access token obtained manually using the auth code grant flow - so I have no idea why I'm now getting the 401. It's as if the token I'm getting from the client credentials flow doesn't have the permissions I know I've set for it in AAD.
 
Any pointers for troubleshooting this further gratefully received.
 
Thanks,
 
Mark.
  • Deleted's avatar
    Deleted
    Nov 15, 2018

    OK, I've figured out the issue - putting this here for others to find if they hit the same problem.

     

    The underlying issue is that the O365 v2.0 Web API is woefully under-documented!

     

    The main issue with using the Client Credentialworflow is that it authenticates the App itself, and not a user, therefore you need to specify the user to send the email from in the URI - but I had to guess that from the v1.0 API!

     

    Changing the URI used with the access token from ;

     

    https://outlook.office365.com/api/v2.0/me/sendmail

    to

    https://outlook.office365.com/api/v2.0/users/<user spn>/sendmail

     

    Then the API knows which user the email is being sent from and the token which has the 'Send mail for any user' claim is accepted and the email is sent.

     

    Hope others find this useful.

     

    Regards,

     

    Mark. 

5 Replies

  • So after a bit more reading I've discovered that you can't use a client credentials flow using a shared secret to send mail, you have to use a client credentials flow using certificates! Wish that ere documented better.

     

    So I created a self-signed cert, uploaded it against the web app in Azure AD and updated the manifest to include the cert details. Then I tried rolling my own JWT and trying to sign it with RS256 to no avail. After more digging it's apparent that your self signed cert has to be created using the right provider or it won't be able to be used to sign RSA - only HMAC! - So switched from makecert to powershell to create the new cert - re-uploaded the public cert and re-updated the manifest on the app, then gave up on all the work I'd done earlier on JWT stuff and fell back on .NET ADAL modules.

     

    After all that I can successfully obtain an authentication token using client credential flow and certificates - and analysing the JWT I get back I can see I have permissions to Send.Mail, but now when I try and use that token I'm getting a 403 Forbidden error!!

     

    Who knew it could be so hard to send an email!!

     

    Anyone have any idea what else I can try?

     

    Thanks,

     

    Mark.

    • Deleted's avatar
      Deleted

      OK, I've figured out the issue - putting this here for others to find if they hit the same problem.

       

      The underlying issue is that the O365 v2.0 Web API is woefully under-documented!

       

      The main issue with using the Client Credentialworflow is that it authenticates the App itself, and not a user, therefore you need to specify the user to send the email from in the URI - but I had to guess that from the v1.0 API!

       

      Changing the URI used with the access token from ;

       

      https://outlook.office365.com/api/v2.0/me/sendmail

      to

      https://outlook.office365.com/api/v2.0/users/<user spn>/sendmail

       

      Then the API knows which user the email is being sent from and the token which has the 'Send mail for any user' claim is accepted and the email is sent.

       

      Hope others find this useful.

       

      Regards,

       

      Mark. 

  • Do check the token for the scope/permissions, you can parse it on jwt.ms/jwt.io or via PowerShell as well if needed.

  • Why not use the Send-Email cmdlet? You can use Office 365 as your SMTP Server and compose also an HTML message to be send in your script
    • Mark Blackburn's avatar
      Mark Blackburn
      Copper Contributor

      As I stated at the beginning of the post - I can't use SMTP as it is blocked

Resources