SOLVED

Unauthorized attempting to send Email with valid access token

Deleted
Not applicable

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.
5 Replies
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

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

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

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.

best response
Solution

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. 

1 best response

Accepted Solutions
best response
Solution

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. 

View solution in original post