Skip to content

Daemon Sample always returns Unauthorized although correct permissions are assigned #103

@owebeewan

Description

@owebeewan

Please provide us with the following information:

This issue is for a: (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request
- [ ] documentation issue or request
- [ ] regression (a behavior that used to work and stopped in a new release)

Minimal steps to reproduce

Run the attached code. The response from httpClient.SendAsync(request) always returns Unauthorized

Any log messages given by the failure

None

Expected/desired behavior

Authorized!

OS and Version?

Windows 11 Home

Versions

10.0.26100

Mention any other details that might be useful

Unauthorized error:
{"error":{"code":"OrganizationFromTenantGuidNotFound","message":"The tenant for tenant guid '0ac2da02-f40e-4889-977c-67abc6b1ea17' does not exist.","innerError":{"oAuthEventOperationId":"be6f1ba5-ec8f-4bc1-8886-79677c609d98","oAuthEventcV":"dG+k3EExfgbY2xaPnR0BKA.1.1","errorUrl":"https://aka.ms/autherrors#error-InvalidTenant","requestId":"6646c447-ce81-4d17-b16a-38069494e270","date":"2025-01-11T23:47:34"}}}

The tenant ID is correctly set. I promise

I've correctly entered all GUIDs and verified 9 times. Authenticates fine. I have all mail permissions set to application scope and assigned by admin.

using Microsoft.Identity.Client;
using System.Net.Http.Headers;
using System.Text.Encodings.Web;
using System.Text.Json;

// based on https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-daemon-dotnet-acquire-token

// entra Tenant ID     = 0ac2da02-f40e-4889-977c-67abc6b1ea17
// az portal Tenant ID = 0ac2da02-f40e-4889-977c-67abc6b1ea17
var tenantId = "00000000-f40e-4889-977c-67abc6b1ea17";

// entra Client ID     = 0c29ca3a-0bcf-4861-8430-e5f47abf0972
// az portal Client ID = 0c29ca3a-0bcf-4861-8430-e5f47abf0972
var clientId = "11111111-0bcf-4861-8430-e5f47abf0972";

var clientSecret = "secret"; // the client secret obtained from the Microsoft Entra admin center",

// entra Client Object ID =     8e7c972c-289c-4fe2-b53e-a8a3dbe797cf (service principal ID - enterprise app)
// az portal Client Object ID = 088843fe-1ace-40d2-9c26-3f9a5a358a03
// not sure why I have two different Object ID's for the application but the azure portal one authenticates and the entra one does not
var clientObjectId = "22222222-1ace-40d2-9c26-3f9a5a358a03";

// entra User Object ID = 17142bbb-92cf-4c7d-b91b-d017534463d1
// az portal User Object ID = 17142bbb-92cf-4c7d-b91b-d017534463d1
var userId = "33333333-92cf-4c7d-b91b-d017534463d1";

var authority = $"https://login.microsoftonline.com/{tenantId}";

/**
 * In Azure app permissions - these and many more:
 * Mail.Read = Both Application and Delegated
 * User.Read = Both Application and Delegated
 * Mail.ReadWrite = Both Application and Delegated
 * MailboxFolder.Read = Both Application and Delegated
 * MailboxSettings.Read = Both Application and Delegated
 * Mail.Read.Shared = Delegated
 * Mail.Send = Both Application and Delegated
 */

// This app instance should be a long-lived instance because
// it maintains the in-memory token cache.
var app = ConfidentialClientApplicationBuilder.Create(clientId)
                    .WithTenantId(tenantId)
                    .WithClientSecret(clientSecret)
                    .WithAuthority(new Uri(authority))
                    .Build();

// Acquire token for client credentials flow
var authenticationResult = await app.AcquireTokenForClient(["https://graph.microsoft.com/.default"]).ExecuteAsync();
var bearerToken = authenticationResult.AccessToken;

var httpClient = new HttpClient();
await Authenticate(httpClient, clientObjectId, bearerToken);

// At this point I have successful authentication

await GetMailboxesAsync(httpClient, bearerToken, clientObjectId);  // <== this is where I get unauthorized

await SendEmailAsync(httpClient, userId, bearerToken); // <== this is where I get unauthorized
Console.ReadLine();

static async Task Authenticate(HttpClient httpClient, string clientObjectId, string bearerToken)
{
    using var graphRequest = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/applications/{clientObjectId}");
    graphRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
    graphRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    var graphResponseMessage = await httpClient.SendAsync(graphRequest);

    if (graphResponseMessage.StatusCode == System.Net.HttpStatusCode.Forbidden)
    {
        Console.WriteLine("Access denied. Please check the permissions granted to the application.");
        return;
    }

    using var graphResponseJson = JsonDocument.Parse(await graphResponseMessage.Content.ReadAsStreamAsync());
    Console.WriteLine(JsonSerializer.Serialize(graphResponseJson,
        new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }));
}

static async Task GetMailboxesAsync(HttpClient httpClient, string token, string userId)
{
    // Call the GetMailboxes endpoint to get a list of mailboxes.
    var getMailboxesRequest = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/users/{userId}/mailFolders");
    getMailboxesRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
    var getMailboxesResponse = await httpClient.SendAsync(getMailboxesRequest);
    if (getMailboxesResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        Console.WriteLine($"Access {getMailboxesResponse.StatusCode}. Why is this unauthorized?");   // <== this is where I get unauthorized
        return;
    }
    if (!getMailboxesResponse.IsSuccessStatusCode)
    {
        Console.WriteLine($"Access {getMailboxesResponse.StatusCode}. ??");
        return;
    }

    getMailboxesResponse.EnsureSuccessStatusCode();

    // Parse the response to get a list of mailboxes.
    var mailboxesJson = await getMailboxesResponse.Content.ReadAsStringAsync();
    var mailboxes = JsonDocument.Parse(mailboxesJson).RootElement;
    foreach (var mailbox in mailboxes.EnumerateArray())
    {
        Console.WriteLine(mailbox.GetProperty("displayName").GetString());
    }

}
static async Task SendEmailAsync(HttpClient httpClient, string userId, string token)
{
    var emailMessage = new
    {
        message = new
        {
            subject = "Test Email",
            body = new
            {
                contentType = "Text",
                content = "Hello, this is a test email!"
            },
            toRecipients = new[]
            {
                new
                {
                    emailAddress = new
                    {
                        address = "wes.smith@outlook.com"
                    }
                }
            }
        },
        saveToSentItems = "true"
    };


    var requestContent = new StringContent(JsonSerializer.Serialize(emailMessage)); 
    requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 
    var request = new HttpRequestMessage(HttpMethod.Post, $"https://graph.microsoft.com/v1.0/users/{userId}/sendMail") 
        { Content = requestContent }; 
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
    var response = await httpClient.SendAsync(request);

    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        Console.WriteLine($"Access {response.StatusCode}. Why is this unauthorized?");     // <== this is where I get unauthorized
        return;
    }
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine($"Access {response.StatusCode}. ??");
        return;
    }

    Console.WriteLine(response.StatusCode);
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions