I'm working on a scenario where users will authenticate and grant consent to my application using a Windows Forms utility. This utility will allow users to connect their Entra ID or personal Microsoft accounts (e.g., Outlook) to my services.
I need to use the authenticated user's access token to send emails on their behalf from a separate background application running on the same PC.
Currently, I have a similar setup for Gmail accounts:
- A Windows Forms utility allows users to configure their Gmail account.
- The utility stores the OAuth 2.0 response JSON token file in a specific path on the user's system.
- A separate background application reads this JSON token file and uses it to send emails.
- The background application also handles token refresh when needed.
I'm trying to replicate this behavior for Exchange accounts using the Microsoft Identity platform and Microsoft Graph API.
- I'm using InteractiveBrowserCredential with TokenCachePersistenceOptions to store the authentication tokens.
- The token cache is being stored in the user's AppData folder, under the name specified in TokenCachePersistenceOptions.Name.
- However, when my background application attempts to use the same TokenCachePersistenceOptions.Name to access the cached token, a browser window is always launched for authentication.
- I expect the background application to silently refresh the access token and send emails without user interaction, as the user has already granted consent in the Windows Forms utility.
Win Forms utility code:
using Azure.Identity;
using Microsoft.Graph;
using System;
using System.Threading.Tasks;
// ...
public async Task AuthenticateAndStoreToken()
{
var options = new InteractiveBrowserCredentialOptions
{
TenantId = "common", // For Entra and Personal accounts
ClientId = "your_client_id", // Replace with your Client ID
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("https://localhost/"),
TokenCachePersistenceOptions = new TokenCachePersistenceOptions
{
Name = "MyEmailAppCache"
}
};
var credential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(credential, new[] { "User.Read", "Mail.Send" });
try
{
var me = await graphClient.Me.GetAsync();
Console.WriteLine($"Authenticated as: {me.DisplayName}");
}
catch (Exception ex)
{
Console.WriteLine($"Authentication Error: {ex.Message}");
}
}
Background app to send email:
var options = new InteractiveBrowserCredentialOptions
{
TenantId = "common",
ClientId = "your_client_id", // Replace with SAME Client ID
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("https://localhost/"),
TokenCachePersistenceOptions = new TokenCachePersistenceOptions
{
Name = "MyEmailAppCache" // Same cache name
}
};
var credential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(credential, new[] { "Mail.Send" });
try
{
var message = new Message
{
// ... (construct email message)
};
var sendMailPostRequestBody = new Microsoft.Graph.Me.SendMail.SendMailPostRequestBody
{
Message = message,
SaveToSentItems = true
};
await graphClient.Me.SendMail.PostAsync(sendMailPostRequestBody);
Console.WriteLine("Email sent successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"Send Email Error: {ex.Message}");
}
}
- I'm using the same TokenCachePersistenceOptions.Name and same client_id in both applications.
- Both applications are running under the same user account.
- The token cache file is being created in the AppData folder, but it seems to be replaced rather than reused.
What could be causing the browser window to appear every time in the background application? Is there a recommended approach for sharing authentication tokens between separate applications in this scenario? Are there any common pitfalls or best practices I should be aware of?
I'm working on a scenario where users will authenticate and grant consent to my application using a Windows Forms utility. This utility will allow users to connect their Entra ID or personal Microsoft accounts (e.g., Outlook) to my services.
I need to use the authenticated user's access token to send emails on their behalf from a separate background application running on the same PC.
Currently, I have a similar setup for Gmail accounts:
- A Windows Forms utility allows users to configure their Gmail account.
- The utility stores the OAuth 2.0 response JSON token file in a specific path on the user's system.
- A separate background application reads this JSON token file and uses it to send emails.
- The background application also handles token refresh when needed.
I'm trying to replicate this behavior for Exchange accounts using the Microsoft Identity platform and Microsoft Graph API.
- I'm using InteractiveBrowserCredential with TokenCachePersistenceOptions to store the authentication tokens.
- The token cache is being stored in the user's AppData folder, under the name specified in TokenCachePersistenceOptions.Name.
- However, when my background application attempts to use the same TokenCachePersistenceOptions.Name to access the cached token, a browser window is always launched for authentication.
- I expect the background application to silently refresh the access token and send emails without user interaction, as the user has already granted consent in the Windows Forms utility.
Win Forms utility code:
using Azure.Identity;
using Microsoft.Graph;
using System;
using System.Threading.Tasks;
// ...
public async Task AuthenticateAndStoreToken()
{
var options = new InteractiveBrowserCredentialOptions
{
TenantId = "common", // For Entra and Personal accounts
ClientId = "your_client_id", // Replace with your Client ID
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("https://localhost/"),
TokenCachePersistenceOptions = new TokenCachePersistenceOptions
{
Name = "MyEmailAppCache"
}
};
var credential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(credential, new[] { "User.Read", "Mail.Send" });
try
{
var me = await graphClient.Me.GetAsync();
Console.WriteLine($"Authenticated as: {me.DisplayName}");
}
catch (Exception ex)
{
Console.WriteLine($"Authentication Error: {ex.Message}");
}
}
Background app to send email:
var options = new InteractiveBrowserCredentialOptions
{
TenantId = "common",
ClientId = "your_client_id", // Replace with SAME Client ID
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("https://localhost/"),
TokenCachePersistenceOptions = new TokenCachePersistenceOptions
{
Name = "MyEmailAppCache" // Same cache name
}
};
var credential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(credential, new[] { "Mail.Send" });
try
{
var message = new Message
{
// ... (construct email message)
};
var sendMailPostRequestBody = new Microsoft.Graph.Me.SendMail.SendMailPostRequestBody
{
Message = message,
SaveToSentItems = true
};
await graphClient.Me.SendMail.PostAsync(sendMailPostRequestBody);
Console.WriteLine("Email sent successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"Send Email Error: {ex.Message}");
}
}
- I'm using the same TokenCachePersistenceOptions.Name and same client_id in both applications.
- Both applications are running under the same user account.
- The token cache file is being created in the AppData folder, but it seems to be replaced rather than reused.
What could be causing the browser window to appear every time in the background application? Is there a recommended approach for sharing authentication tokens between separate applications in this scenario? Are there any common pitfalls or best practices I should be aware of?
Share Improve this question asked Mar 19 at 16:33 pradeeppradeep 3293 silver badges17 bronze badges 1- You would need to impersonate the User to do what you want. It would be better to setup a Mail Relay to do what you want. See support.microsoft/en-us/topic/… and duocircle/content/outbound-smtp-relay-service/… – jdweng Commented Mar 19 at 16:48
1 Answer
Reset to default 1What you're missing is the AuthenticationRecord
that tells the background instance of your app which account from the cache it should use. This is described in details at TokenCachePersistenceOptions Class (Azure.Identity) and in Token caching in the Azure Identity client library.
You obtain the AuthenticationRecord
by forcing an initial interactive authentication in your forms app by calling AuthenticateAsync()
on the InteractiveBrowserCredential
:
AuthenticationRecord authRecord = await credential.AuthenticateAsync(
new TokenRequestContext(new[] { "User.Read" })
);
Now, you need to save this so that it can be picked up by the background instance of your app later. A simple way to do this is to serialize it (using the provided method) and store the result in a text file:
using (var authRecordStream = new FileStream(AUTH_RECORD_PATH, FileMode.Create, FileAccess.Write))
{
await authRecord.SerializeAsync(authRecordStream);
}
From the background instance of your app, you deserialize the AuthenticationRecord
from the file, and provide it to the InteractiveBrowserCredential
(along with the name of the persisted token cache, like you're already doing):
AuthenticationRecord authRecord;
using (var authRecordStream = new FileStream(AUTH_RECORD_PATH, FileMode.Open, FileAccess.Read))
{
authRecord = await AuthenticationRecord.DeserializeAsync(authRecordStream);
}
var options = new InteractiveBrowserCredentialOptions
{
// ...
TokenCachePersistenceOptions = new TokenCachePersistenceOptions { Name = "MyEmailAppCache" },
AuthenticationRecord = authRecord
};
With this, the background instance of your app will know which of the (potentially many) accounts in the persisted cache it should use, and it can proceed silently when invoked by the Microsoft Graph SDK.