1

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:

  1. Create an App Registration in my Azure account. Set supported types to "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)"
  2. Added the following API permissions:enter image description here
  3. Create a secret to be used with ClientSecretCredential
  4. I invited my @outlook.com account to be added as a user in my Azure Active Directory. Not sure if this is needed or not.

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

4
  • It should be not able to query mails for @outlook.com user. 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. Commented Feb 13, 2023 at 7:29
  • and the api will return the mails in this account. I assume it's the same for a guest account. Your guest account can be an external email address, after it is invited into your tenant, it really user principle is like what you showed someone_outlook.com#EXT#@MyAzureAdminEmail.onmicrosoft.com, then you can also assign M365 license to this account and query mails for email address someone_outlook.com#EXT#@MyAzureAdminEmail.onmicrosoft.com but not for email address [email protected] Commented Feb 13, 2023 at 7:32
  • The Microsoft Graph API supports accessing data in users' primary mailboxes and in shared mailboxes. The data can be calendar, mail, or personal contacts stored in a mailbox in the cloud on Exchange Online as part of Microsoft 365, or on Exchange on-premises in a hybrid deployment. From this document. Commented Feb 13, 2023 at 11:37
  • 1
    @TinyWang thanks for your comments. I will have to go with delegate permissions. I will post an answer for how I did it for others to see. Commented Feb 14, 2023 at 1:44

1 Answer 1

2

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

  1. Add a redirect URI that will receive the authentication code from Microsoft.
  2. Set the Supported account types to the "... Microsoft accounts" type enter image description here

Add the following API permissions: enter image description here

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:

  1. When the user hits the controller, the view is rendered with the authorization hyper link.
  2. 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.
  3. 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.
  4. 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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.