Exchange Audit Log Endpoint Returns 401

66 Views Asked by At

I'm trying to retrieve Exchange Audit logs using manage.office.com endpoint.

This is the code:

public class ExchangeAuditLogReaderHelper
{
    private readonly string _tenantId;
    private readonly string _clientId;
    private readonly string _clientSecret;
    private readonly string _apiUrl = "https://manage.office.com/api/v1.0/{0}/activity/feed/subscriptions/content?contentType=Audit.Exchange&startTime={1:yyyy-MM-dd'T'HH:mm:ss}&endTime={2:yyyy-MM-dd'T'HH:mm:ss}";

    public ExchangeAuditLogReaderHelper(string tenantId, string clientId, string clientSecret)
    {
        _tenantId = tenantId;
        _clientId = clientId;
        _clientSecret = clientSecret;
    }

    public async Task<string> GetAuditLogsAsync(DateTime startTime, DateTime endTime)
    {
        var accessToken = await GetAccessToken();

        var url = string.Format(_apiUrl, _tenantId, startTime, endTime);

        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await client.GetAsync(url);

            if (response.IsSuccessStatusCode)
            {
                var contentString = await response.Content.ReadAsStringAsync();
                // Parse the JSON response and extract audit log entries (implementation omitted)
                return contentString;
            }
            else
            {
                throw new Exception($"Error retrieving audit logs: {response.StatusCode}");
            }
        }
    }

    private async Task<string> GetAccessToken()
    {
        var authority = $"https://login.microsoftonline.com/{_tenantId}";
        var authenticationContext = new AuthenticationContext(authority);
        var clientCredential = new ClientCredential(_clientId, _clientSecret);

        var userAssertion = await authenticationContext.AcquireTokenAsync("https://manage.office.com", clientCredential);
        return userAssertion.AccessToken;
    }
}

I have done the following steps:

  • Create an Office 365 tenant. (Get TenantID.)
  • Create an Enterprise App. (Get Client ID.)
  • Create a Secret. (Get Client Secret.)
  • Give the Enterprise App delegated permission ActivityFeed.Read and ActivityFeed.ReadDlp.
  • Grant admin consent to the permissions.

I ran code with the values I have created, but I'm getting 401. Am I missing permissions for this?

1

There are 1 best solutions below

1
Sridevi On BEST ANSWER

The error might occur if you granted permissions of Delegated type that won't work with app-only flow.

Initially, I too got same error when I granted Delegated permissions in the application and tried to call API like this:

enter image description here

When I decoded this access token in jwt.ms, it does not have roles claim in it:

enter image description here

To resolve the error, make sure to grant permissions of Application type while using app-only flows:

enter image description here

When I ran below code after granting permissions of Application type, I got the response (blank as I don't have any):

using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

public class ExchangeAuditLogReaderHelper
{
    private readonly string _tenantId;
    private readonly string _clientId;
    private readonly string _clientSecret;
    private readonly string _apiUrl = "https://manage.office.com/api/v1.0/{0}/activity/feed/subscriptions/content?contentType=Audit.Exchange&startTime={1:yyyy-MM-dd'T'HH:mm:ss}&endTime={2:yyyy-MM-dd'T'HH:mm:ss}";

    public ExchangeAuditLogReaderHelper(string tenantId, string clientId, string clientSecret)
    {
        _tenantId = tenantId;
        _clientId = clientId;
        _clientSecret = clientSecret;
    }

    public async Task<string> GetAuditLogsAsync(DateTime startTime, DateTime endTime)
    {
        var accessToken = await GetAccessToken();

        var url = string.Format(_apiUrl, _tenantId, startTime, endTime);

        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await client.GetAsync(url);

            if (response.IsSuccessStatusCode)
            {
                var contentString = await response.Content.ReadAsStringAsync();
                return contentString;
            }
            else
            {
                var statusCode = (int)response.StatusCode;
                var errorMessage = await response.Content.ReadAsStringAsync();
                throw new Exception($"Error retrieving audit logs. Status code: {statusCode}. Error Message: {errorMessage}");
            }
        }
    }

    private async Task<string> GetAccessToken()
    {
        var authority = $"https://login.microsoftonline.com/{_tenantId}";
        var authenticationContext = new AuthenticationContext(authority);
        var clientCredential = new ClientCredential(_clientId, _clientSecret);

        var userAssertion = await authenticationContext.AcquireTokenAsync("https://manage.office.com", clientCredential);

        // Print the access token to the console
        Console.WriteLine("Access Token: " + userAssertion.AccessToken);

        return userAssertion.AccessToken;
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        // Replace these with your actual values
        var tenantId = "tenantId";
        var clientId = "appId";
        var clientSecret = "secret";

        var helper = new ExchangeAuditLogReaderHelper(tenantId, clientId, clientSecret);

        // Specify start and end time as required
        var startTime = DateTime.UtcNow.AddDays(-1);
        var endTime = DateTime.UtcNow;

        try
        {
            var logs = await helper.GetAuditLogsAsync(startTime, endTime);
            Console.WriteLine(logs);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\n{ex.Message}");
        }
    }
}

Response:

enter image description here

You can also decode this access token in jwt.ms and check for roles claim value to know what permissions token have:

enter image description here