Using Graph API 5 and Azure Identity
App Registration Details:
-- Platform Configuration: Native Client
-- Allow Public Client Flows = "yes"
-- Delegated permissions: Files.ReadWrite.All, User.Read
OS: Windows 11 Enterprise
Application Type: Console
.NET Version: Framework 4.8.1
Admin consent has already been granted to everyone in the organization. The end user will not get a screen that prompts for permissions.
The code does exactly what it's supposed to do, but I notice that when the Graph API call is made, a blank window pops-up for about a second then disappears.
Is that normal? Shouldn't the code just run without any pop ups?
The code shown below is what I use to authenticate.
The end user calls GetGraphServiceClient to get a GraphServiceClient object.
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Identity;
using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using static System.Net.WebRequestMethods;
namespace Authenticator
{
public class GraphAuthentication
{
private static GraphServiceClient graphClient = null;
private static readonly string TOKEN_CACHE_NAME = "TokenCache.bin";
private static readonly string tokenCachePath = Environment.GetEnvironmentVariable("APPDATA") + "\\" + TOKEN_CACHE_NAME;
private static readonly string clientId = "-- CLIENT ID --";
private static readonly string tenantId = "-- TENANT ID --";
private static readonly string scopeStrings = "https://graph.microsoft.com/.default";
private static readonly string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
private static InteractiveBrowserCredential interactiveCredential;
private static readonly InteractiveBrowserCredentialOptions options = new InteractiveBrowserCredentialOptions
{
TenantId = tenantId,
ClientId = clientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri(redirectUri),
BrowserCustomization = new BrowserCustomizationOptions { UseEmbeddedWebView = true },
TokenCachePersistenceOptions = new TokenCachePersistenceOptions { Name = TOKEN_CACHE_NAME },
Diagnostics =
{
ApplicationId = DiagnosticsOptions.DefaultApplicationId,
LoggedHeaderNames = { "x-ms-request-id" },
LoggedQueryParameters = { "api-version" },
IsLoggingEnabled = true,
IsLoggingContentEnabled = true
}
};
public static GraphServiceClient GetGraphServiceClient(string loginHint)
{
if (graphClient != null) return graphClient;
options.LoginHint = loginHint;
List<string> scopes = new List<string> { scopeStrings };
AzureEventSourceListener listener = new AzureEventSourceListener((e, message) =>
{
// Only log messages from Azure-Core event source
if (e.EventSource.Name == "Azure-Core")
{
using (StreamWriter writer = new StreamWriter("graphCalls.log", true))
{
writer.Write(message);
}
}
},
level: System.Diagnostics.Tracing.EventLevel.Verbose);
AuthenticationRecord authRecord;
try
{
if (System.IO.File.Exists(tokenCachePath) == false)
{
// https://docs.microsoft.com/dotnet/api/azure.identity.interactivebrowsercredential
interactiveCredential = new InteractiveBrowserCredential(options);
TokenRequestContext tokenRequestContext = new TokenRequestContext(new[] { scopeStrings });
// Call AuthenticateAsync to fetch a new AuthenticationRecord.
authRecord = interactiveCredential.Authenticate(tokenRequestContext);
// Serialize the AuthenticationRecord to disk so that it can be re-used across executions of this initialization code.
using (var authRecordStream = new System.IO.FileStream(tokenCachePath, System.IO.FileMode.Create, System.IO.FileAccess.Write))
{
authRecord.SerializeAsync(authRecordStream).Wait();
}
}
else
{
using (var authRecordStream = new System.IO.FileStream(tokenCachePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
authRecord = AuthenticationRecord.DeserializeAsync(authRecordStream).Result;
System.Diagnostics.Debug.WriteLine("Deserialized AuthenticationRecord.");
options.AuthenticationRecord = authRecord;
interactiveCredential = new InteractiveBrowserCredential(options);
}
}
graphClient = new GraphServiceClient(interactiveCredential, scopes);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
return graphClient;
}
}
}
Note that: When using the InteractiveBrowserCredential
, the blank window that appears when a Graph API request is normal. This is so that the credential can authenticate the user by means of an embedded web view. After the user has authenticated and granted permission, the window should close.
ClientSecretCredential
where it does not require user interaction and can be used to authenticate without displaying a login page.In your code, you can modify it by calling any Microsoft Graph call like below:
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.Me.GetAsync();
References:
Get a user - Microsoft Graph v1.0
OAuth 2.0 client credentials flow on the Microsoft identity platform - Microsoft Entra