We're trying to implement an unattended C# application that automates some email activities - the app is supposed to authenticate on behalf of users with standard credentials (simple username and password), using Microsoft.Identity.Client library (4.47.2). Our environment requires that it is easy to setup and completely void of human interaction (thus we can't use integrated windows auth or other methods). After authentication and authorization, we're planning on using the Microsoft Graph nuget library to do the required graph calls (such as accessing mail folders, messages, etc.).
Our situation: we're trying to authenticate with a client app instance, using the AcquireTokenByUsernamePassword call, retrieve a token and create a GraphServiceClient object. We've managed to implement this and are able to fetch user details (such as email, name and so on) but when trying to access other graph aspects (such as MailFolders) we keep getting ErrorAccessDenied:
Code: ErrorAccessDenied
Message: Access is denied. Check credentials and try again.
Here is the code we're using:
var scopes = new[] { "https://graph.microsoft.com/.default" };
string clientId = "app_client_id_from_azure_portal";
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.Build();
string pwd = "my_password";
string username = "my_email";
SecureString pass = new SecureString();
foreach (char c in pwd)
pass.AppendChar(c);
AuthenticationResult res = await app.AcquireTokenByUsernamePassword(scopes, username, pass).ExecuteAsync();
GraphServiceClient client = new GraphServiceClient("https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", res.AccessToken);
}));
var inbox = await client.Me.MailFolders["inbox"].Request().GetAsync();
Client ID is obtained from the Azure Portal, with the app that's registered. The username in question is part of the AD directory for this app and has the app authorized. Permissions for the app are also granted as described in the docs:
Can anyone shed some light into what we're doing wrong? We've been trying to get this working for over two weeks.
Note: I'm sure this is an authorization issue, since we're able to get user details but not access lower level components. Mainly with the token that's being issued, because if we copy/paste and hardcode the token we get from the Graph Explorer (https://developer.microsoft.com/en-us/graph/graph-explorer) everything works correctly and we're not getting the access denied error anymore.
CodePudding user response:
I tried to reproduce the same in my environment and got below results:
I registered one multi-tenant Azure AD application and added API permissions like below:
When I ran the same code as you by including user credentials, I got same error as below:
To know what permissions your access token has, print the token by adding below line in your code:
Console.WriteLine("Token: " res.AccessToken);
Response:
Now, decode the above token in jwt.ms and you can see permissions in scp
claim like below:
The error usually occurs if access token does not have required permissions to list mail folders such as Mail.ReadBasic, Mail.Read, Mail.ReadWrite
You can also decode the token from Graph Explorer to know what permissions it has, by pasting the token in jwt.ms .
To resolve the error, add at least one of the above Delegated permissions in your application:
When I ran the code again, I got the results successfully like below:
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Security;
var scopes = new[] { "https://graph.microsoft.com/.default" };
string clientId = "806d0e6f-03a7-4135-839e-969xxxxxxx8d2";
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.Build();
string pwd = "xxxxxxxxxxx";
string username = "[email protected]";
SecureString pass = new SecureString();
foreach (char c in pwd)
pass.AppendChar(c);
AuthenticationResult res = await app.AcquireTokenByUsernamePassword(scopes, username, pass).ExecuteAsync();
Console.WriteLine("Token: " res.AccessToken);
GraphServiceClient client = new GraphServiceClient("https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", res.AccessToken);
}));
var inbox = await client.Me.MailFolders["inbox"].Request().GetAsync();
Console.WriteLine("\nID: " inbox.Id);
Console.WriteLine("Folder Name: " inbox.DisplayName);
Console.WriteLine("Parent Folder ID: " inbox.ParentFolderId);
Console.WriteLine("Total Messages Count: " inbox.TotalItemCount);
Console.WriteLine("Unread Messages Count: " inbox.UnreadItemCount);
Output:
When I decoded the above token, it contains Mail.Read
and Mail.ReadBasic
permissions like below:
To fetch user details such as email, name etc., User.Read
Delegated permission is enough but for listing mail folders, you need extra permissions.
In your case, make sure to grant at least one of the Delegated permissions like Mail.Read, Mail.ReadBasic, Mail.ReadWrite