I am using this tutorial as a base to be able to read mail from my @outlook.com account using Microsoft Graph: https://learn.microsoft.com/en-us/graph/tutorials/dotnet-app-only?tabs=aad
I've done the following:
Here is the code that initializes the GraphServiceClient:
if (_clientSecretCredential == null)
{
_clientSecretCredential = new ClientSecretCredential(
_settings.TenantId, _settings.ClientId, _settings.ClientSecret, new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud });
}
if (_appClient == null)
{
_appClient = new GraphServiceClient(_clientSecretCredential,
// Use the default scope, which will request the scopes
// configured on the app registration
new[] {"https://graph.microsoft.com/.default"});
}
When I execute the messages endpoint, I am getting two different errors, based on what userId I pass in.
userId = "someone_outlook.com#EXT#@MyAzureAdminEmail.onmicrosoft.com"; // Gives error #1 below
userId = "[email protected]"; // Gives error #2 below
userId = "<object_id_guid>"; // Gives error #2 below
var messages = await _appClient.Users[userId].MailFolders["inbox"].Messages
.Request()
.GetAsync();
Error #1
Error getting users: Status Code: NotFound Microsoft.Graph.ServiceException: Code: Request_ResourceNotFound Message: Resource 'someone_outlook.com' does not exist or one of its queried reference-property objects are not present.
Error #2
Error getting users: Status Code: Unauthorized Microsoft.Graph.ServiceException: Code: OrganizationFromTenantGuidNotFound Message: The tenant for tenant guid '[guid]' does not exist.
I cannot find any examples where someone is reading mail from a Microsoft account (@outlook.com, @hotmail.com, @live.come) using Microsoft Graph and app-only authentication. Is this possible with Microsoft Graph?
Thanks, Garry
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 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:
Thanks, Garry