Based on what @TinyWang stated in the comments above, it does not look like this will be possible.
Instead, I will have to use delegate permissions. The following is a POC application that can use Microsoft Graph API with a personal @Outlook.com account.
Create an App Registration
- Add a redirect URI that will receive the authentication code from Microsoft.
- Set the Supported account types to the "... Microsoft accounts" type

Add the following API permissions:

I created the following settings class to get the settings from the .config file:
public class GraphSettings
{
public string? ClientId { get; set; }
public string? TenantId { get; set; }
public string? ClientSecret { get; set; }
public string? RedirectUri { get; set; }
public string[]? Scopes { get; set; }
}
appsettings.json file:
{
"graphSettings": {
"tenantId": "common",
"clientId": "[set-client-id]",
"clientSecret": "[set-client-secret]",
"RedirectUri": "https://localhost:44308/MailAuthorize",
"scopes": [
"offline_access",
"https://graph.microsoft.com/.default"
]
}
}
Note: for the scopes, offline_access will ensure that the token endpoint will be returned with a refresh token.
Note: for the tenant ID, it needs to be "common" and not your actual tenant ID.
And a Graph helper class:
using Azure.Identity;
using Microsoft.Graph;
using System.Web;
namespace POC.Graph
{
public class GraphHelper
{
private static GraphServiceClient? _graphClient;
public static bool IsInitialized()
{
return (_graphClient != null);
}
public static void Initialize(GraphSettings settings, string authorizationCode)
{
var options = new AuthorizationCodeCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri(settings.RedirectUri)
};
var authCodeCredential = new AuthorizationCodeCredential(settings.TenantId,
settings.ClientId,
settings.ClientSecret,
authorizationCode,
options);
_graphClient = new GraphServiceClient(authCodeCredential, settings.Scopes);
}
public static string GetAuthorizeUri(GraphSettings settings)
{
var clientId = HttpUtility.UrlEncode(settings.ClientId);
var scopes = HttpUtility.UrlEncode(string.Join(" ", settings.Scopes));
var redirectUri = HttpUtility.UrlEncode(settings.RedirectUri);
return $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id={clientId}&state=12345&scope={scopes}&redirect_uri={redirectUri}";
}
public static Task<IMailFolderMessagesCollectionPage> GetMessages()
{
return _graphClient.Me.MailFolders["Inbox"].Messages.Request().GetAsync();
}
}
}
And here is a test Controller:
public class MailAuthorizeController : Controller
{
#region Actions
public IActionResult Index([FromUri] string? code = null)
{
var model = new MailAuthorizeModel();
try
{
var settings = LoadGraphSettings();
model.AuthorizeUri = GraphHelper.GetAuthorizeUri(settings);
if (code != null)
{
GraphHelper.Initialize(settings, code);
}
if (GraphHelper.IsInitialized())
{
var messages = GraphHelper.GetMessages().Result;
if (messages == null)
{
model.Message = "messages is null";
}
else
{
model.Message = "messages count: " + messages.Count().ToString();
}
}
}
catch (Exception ex)
{
model.Message = ex.ToString();
}
return View(model);
}
#endregion
#region Private Methods
private GraphSettings LoadGraphSettings()
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.Development.json", optional: true)
.Build();
return config.GetRequiredSection("GraphSettings").Get<GraphSettings>() ??
throw new Exception("Could not load app settings.");
}
#endregion
}
And the controller's view:
@{
ViewData["Title"] = "Mail Authorize";
var model = (POC.MailAuthorizeModel)Model;
}
<a href="@(model.AuthorizeUri)">Click to Authorize</a>
<span>Message: @(model.Message)</span>
Now to explain what the above does:
- When the user hits the controller, the view is rendered with the authorization hyper link.
- The user clicks the link to authenticated and grant consent to my App Registration. This will return an authentication code back to the controller. This is configured by the redirect URI in the config file.
- The GraphHelper will then be initialized with the config settings and it will use the authentication code returned from the authorize endpoint (it is passed as a query parameter). Behind the scenes the GraphServiceClient will call the token endpoint to get a token that will be used when calling the other Graph endpoints.
- A call to the messages endpoint is made to get the top 10 messages from the user's inbox. This is to test that the GraphServiceClient successfully calls the token endpoint and uses the token to call the messages endpoint.
Thanks,
Garry
@outlook.comuser. Let's assume, I have my personal outlook mail account and you invite me into your tenant, then you can know who sent email to me and what I write in reply? When we using that graph API, it means it can query user emails, and the normal scenario is, you have an account in your tenant like[email protected], then this account doesn't have an email address now, then you by microsoft M365 license and assign to this user, and now this account can be an email address as well, now you can query with graph api.someone_outlook.com#EXT#@MyAzureAdminEmail.onmicrosoft.com, then you can also assign M365 license to this account and query mails for email addresssomeone_outlook.com#EXT#@MyAzureAdminEmail.onmicrosoft.combut not for email address[email protected]primary mailboxesand inshared mailboxes. The data can be calendar, mail, or personal contacts stored in a mailbox in the cloudon Exchange Online as part of Microsoft 365, oron Exchange on-premises in a hybrid deployment. From this document.