Requesting Microsoft Defender HTTP API with .NET and Refit

Requesting Microsoft Defender HTTP API with .NET and Refit

The Microsoft Defender API - or Azure Security Center API - is an interface to obtain security-related information from its M365 or Azure tenant.

Attention: this API is still in preview - and you can definitely feel this. Endpoints or formats change regularly!

This API is therefore not part of the Azure SDK for .NET. It is therefore necessary to develop your own client. In principle, all endpoints of the Microsoft Defender API are OData-capable, but you can simply omit some of them.

Microsoft Defender

Unfortunately, Microsoft Defender is also a product that does not have a unified naming.
In the documentation, Microsoft Defender is listed as a Security Topic in the M365 universe. However, the API Audience is called Security Center (https://api.securitycenter.microsoft.com), which is a different Security Center than Azure Security Center!

Permissons

All necessary permissions are documented in the Microsoft Defender documentation.

In the case of Exposure Score, the API permission Score.Read.All from WindowsDefenderATP is required.

Refit

Refit is an open source project under the .NET Foundation hat that is perfect for any kind of HTTP based APIs.

The big advantage is that you only need to describe the API with the help of an interface; the actual code is generated during compilation using this contract (based on source code generators).

Refit takes care of all the HTTP handling, as well as serializing it into a response model, which you then only need to specify as a return parameter.

// API Response Model
public record class MicrosoftDefenderExposureScoreResponseModel
    (DateTimeOffset Time, double Score);

// Refit API Contract
public interface IMicrosoftDefenderHttpApiClient
{
    [Get("/api/exposureScore")]
    Task<MicrosoftDefenderExposureScoreResponseModel> GetExposureScore(
        [Authorize("Bearer")] string bearerToken);
}

Refit offers several ways to enrich requests with bearer tokens or authentication headers.
On the one hand, there are handlers that authenticate each request immediately; however, the token can also be supplied dynamically using parameters.

In my case I have to query the exposure score of several tenants, so I cannot use an AuthHandler. I have to get the token manually.

public class MicrosoftDefenderBearerTokenClientProvider
{
    private const string _authority = "https://login.microsoftonline.com";
    private const string _audience = "https://api.securitycenter.microsoft.com";

    private static string[] s_scopes = new[] { $"{_audience}/.default" };

    public async Task<AuthenticationResult> GetToken
        (string tenantId, string clientId, string secret)
    {
        IConfidentialClientApplication app =
            ConfidentialClientApplicationBuilder
                .Create(clientId).WithClientSecret(secret)
                .WithAuthority($"{_authority}/{tenantId}").Build();

        AuthenticationResult authResult =
            await app.AcquireTokenForClient(s_scopes)
                .ExecuteAsync().ConfigureAwait(false);

        return authResult;
    }
}

The request

Now, in principle, all that is needed is two lines of code and we have full authenticated communication to the Microsoft Defender API with refit.

public async Task Sample()
{
    string tenantId = "tenant id guid here";
    string clientId = "client id guid here";
    string clientSecret = "client secret string here";

    // create client and token provider
    IMicrosoftDefenderHttpApiClient api =
        RestService.For<IMicrosoftDefenderHttpApiClient>
            ("https://api.securitycenter.microsoft.com");
    MicrosoftDefenderBearerTokenClientProvider provider = new();

    // request token
    AuthenticationResult auth = await provider
        .GetToken(tenantId, clientId, clientSecret);

    // call Microsoft Defender API
    MicrosoftDefenderExposureScoreResponseModel model
        = await api.GetExposureScore(auth.AccessToken);

    Console.WriteLine("Score: " + model.Score);
}