Blog Post: Blog, security, tech

Routing email via Microsoft 365 using OAUTH2

A company reached out to Halon with an interesting use-case they were hoping the Halon MTA would support.  They wanted to relay some select traffic out through Microsoft 365’s Exchange Online (hereafter 365) on behalf of their customers. At a first glance, this didn’t sound too complicated, but there turned out to be a few challenges. The company wanted to authenticate at 365 using OAUTH2, and the solution had to be multi-tenanted as the functionality was part of a product they develop and sell to their customers.

Using OAUTH2 was an end-customer requirement. They didn’t want to rely on what’s called “legacy authentication” in 365, which is considered insecure by many. This ruled out regular SMTP AUTH (such as PLAIN, LOGIN or various CRAM methods).

We’ve had customers build OAUTH2 authentication methods before using our AUTH hooks, so the concept was familiar to us. We had, however, not yet helped anyone relay outbound messages via 365 with OAUTH2.

We chose to use Microsoft’s “Microsoft Authentication Library (MSAL) for Go” library which, unsurprisingly, had good 365 support. The Halon MTA has the ability to load C ABI compatible plugins, and it didn’t take long for us to wrap this library in a plugin we called “msal”. The MSAL plugin is really easy to use. You configure a public client with the outlook.office.com/SMTP.Send API scope, and then call the msal() function to request a valid OAUTH2 token. The plugin will take care of everything, including updating the token when it expires. 

$r = msal($tenant["id"], [
			"username" => $tenant["username"],
			"password" => $tenant["password"]
		]);
$pwd = "user=".$tenant["username"]."\x01auth=Bearer ".$r["result"]."\x01\x01";

Try([
  "server" => "smtp.office365.com",
  "port" => 587,
  "saslmechanism" => "XOAUTH2",
  "saslpassword" => $pwd
]);

This all was fun and games, but after two weeks it stopped working. At first glance, we didn’t know why, but then realized that Microsoft employs a grace period of two weeks after you enable the security defaults feature (MFA) during which you are still able to send email without MFA.

After quickly going back and regrouping, we found that there was another way of sending email through 365 which supports what is called confidential clients. The Mail.Send HTTP API seemed to allow sending email using OAUTH2 even with MFA enabled. The Halon MTA supports delivering email over HTTP, in the same asynchronous fashion (and with all features such as queue policies available) as with SMTP. HTTP delivery is provided by our http-deliver plugin, and it’s almost as easy as flipping a switch. The code example below shows the final result, which left even the tech team surprised at how quickly the scriptable MTA allowed them to implement this.

$result = msal($tenant["id"]);

Try([
    "plugin" => [
        "id" => "http-deliver",
        "arguments" => [
            "url" => "https://graph.microsoft.com/v1.0/users/".$tenant["username"]."/sendMail",
            "encoder" => "base64",
            "headers" => [
                "Content-Type: text/plain",
                "Authorization: Bearer ".$result["result"]
            ]
        ]
    ]
]);