Search code examples
azureazure-active-directoryexchangewebservices

Exchange Audit Log Endpoint Returns 401


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?


Solution

  • 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