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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions