Sending email through the Graph API securely (application permission scoping)

While interacting with Exchange Online through the Microsoft Graph API is rather easy, it’s really important to understand the scope of the permissions you are assigning.

Sending email through the Graph API securely (application permission scoping)
Senserva

Since the old age, applications that need to send emails through Exchange have always used a service account or a send connector to authenticate to the mail server and send emails. Within a Microsoft 365 environment, service accounts are a burden to manage as they cannot be protected with multifactor authentication and often use legacy authentication (which you really want to disable).

Get the latest Senserva ebook. Created jointly with Microsoft Security. Great overview of Senserva, please take a look. Thanks! - Mark

In order to mitigate this issue, Microsoft has provided the capabilities to use the OAuth2.0 client credential flow (application permissions). Application permissions allow us to authenticate to Azure AD without the need of having an authenticating user in the process. The authentication is entirely based on the tenant ID, client ID and client secret of an application. This form of authentication is great, but you have to be aware it’s pit falls.

In this blog, we’ll walk you through setting up the app registration and sending mails with the API, but also how to limit which mailboxes this app registration can interact with, as application permissions have access to every mailbox by default.

Creating an app registration

The first step in the process, is setting up the app registration. Within the app registration we have to define what permissions the application needs to have (send email) and how we want to authenticate.

In order to create an app registration, navigate to Azure AD and select ‘app registrations’ use this direct link. Choose ‘New registration’ and provide a meaningful name. This name isn’t visible to any end-user, so it’s recommended to use a name by which you can use to easily identify the application in the future. Leave the default account type option selected and leave the redirect URL blank. In our case, users won’t authenticate to the application, so we don’t need this.

After the app is created, you will be reverted to an overview page where you can find application/client ID and the tenant ID. Copy them to your clipboard, as we’ll need them later.

The next step is to provide the correct permissions to the application. Navigate to the ‘API permissions’ tab and select ‘add a permission’.

The bulk of the services within Microsoft 365, use the ‘Microsoft Graph’ API. In our case, this is the API we are using to send email. Next up, choose ‘application permissions’ and find the permission ‘Mail.Send’.

In order to assign these permissions, we need to provide admin consent. To do so, click the ‘Grant admin consent’ button. A pop-up window will appear to authenticate the administrator.

With the correct permissions setup, the last step is to generate a client secret. Navigate to ‘certificates & secrets’ and select ‘New client secret’.

A new pop-up window will appear to provide a description for your secret and select life time. The life time depicts how long the secret will be valid. The application using this app registration should be updated with a valid secret before it expires, as this will prevent unwanted downtime. Rotating your client secrets regularly protects your applications from leaked credentials.    It’s recommended to provide a meaningful description to the secret in order to easily identify it later.

After you add the secret, it will be shown in the portal. Be sure to copy it now as this is only visible once.

Sending an email with the Graph API

Now that we have our app registration created, we can run our script which will send the email. The script consists of four different parts:

  • The variables where we define the authentication details from our app registration (Tenant ID, Client ID and client Secret which we just received) and information for the mail (to/from address, subject and body)
  • The second part retrieves an authentication token for our app registration
  • Next up, we define the body needed for our API call which contains all the details about our mail
  • In the final line, we will send the email using the ‘sendMail’ API

In order to send an email, change the variables to your need and run PowerShell like this:

    $tenantID = "Your tenant ID" 
    $clientID = "Client ID of your app 
    $clientSecret = "Client Secret you created" 
    $mailSubject = "Email Notification from ERP" 
    $toAddress = "john.doe@senserva.com" 
    $FromAddress = "ERPnotification@senserva.com" 
    $bodyContent = "Test email" #Receive an authentication token 
    $AuthBody=@{ client_id=$clientID client_secret=
    $clientSecret scope="https://graph.microsoft.com/.default" grant_type="client_credentials" } 
    $accesstoken = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body 
    $AuthBody -Method Post $accessToken = 
    $accessToken.content | ConvertFrom-Json 
    $authHeader = @{ 'Content-Type'='application/json' 'Authorization'="Bearer " + $accessToken.access_token 'ExpiresOn'=$accessToken.expires_in } 
    #setting up the mail body 
    $body = @" { "message" : { "subject": "$mailSubject", "body" : { "contentType": "html", "content": "$bodyContent" }, "toRecipients": [ { "emailAddress" : { "address" : "$toAddress" } } ] } } "@ 
    #Sending the email 
    Invoke-RestMethod -Headers $authHeader -URI "https://graph.microsoft.com/v1.0/users/$FromAddress/sendMail" -Body $body -Method POST -ContentType 'application/json

The issue

While this script works great, you cannot stop there. Because we are using the application permission ‘Mail.Send’, we are able to send mail as any user within our tenant. While this might sound great at first, this is a huge risk as this would allow an attacker with access to this application to send emails in the name of your CEO, CFO or legal team. It’s of paramount importance that we limit from which accounts this application can send mails.

Limiting application permissions in EXO

In order to limit the scope of Exchange application permissions, we need to create an application access policy. With an application policy we can restrict the permissions of an application to a certain group or deny access to a specific group. Creating and managing application access policies is only possible through PowerShell.

To create a new application policy, connect to Exchange Online:Install-Module -Name ExchangeOnlineManagement Connect-ExchangeOnline

With an active connection to Exchange Online, we can create the application access policy, this is done with the New-ApplicationAccessPolicy. Replace <ClientID> with the Client ID of your newly created app and the parameter ‘PolicyScopeGroupId’ requires a mail enabled security group which should include all the users that the application needs to have access to.New-ApplicationAccessPolicy -AppId <ClientID> -PolicyScopeGroupId <MailEnabledSecurityGroup> -AccessRight RestrictAccess -Description "Limit ERP application to only send emails as ERP users"

After the policy is created, we can use the Test-ApplicationAccessPolicy to verify that the application cannot access any mailbox outside of our group.Test-ApplicationAccessPolicy -AppId bf21029e-e685-4914-a03c-65b3ae79cebd -Identity thijs@365bythijs.be

The AccessCheckResult depicts the fact whether the application is allowed to interact with a specific mailbox.

Summary

While interacting with Exchange Online through the Microsoft Graph API is rather easy, it’s really important to understand the scope of the permissions you are assigning. Application permissions are extremely powerful and should be handled with care. By using application access policies within Exchange Online, we can mitigate the attack surface an application has access to.

Senserva is focused on automating Azure Active Directory security and Azure Sentinel for things like those reviewed in this article.

By Senserva guest Thijs Lecomte