I m using a client credential flow to access the API. I am getting the access token each time client make a call to Web API which seem to me may not be good but not sure why. I looked through web I am getting mix answer, some say Client Credential flow doesn't return refresh token some say possible but it is not clear how. I looked at the project where it seem to store the token in the cache but doesn't show how it can be use when needing to get the access token.
Even if Client Credential flow doesn't support or send refresh token. I am searching for a way to store the access token and use it until is is not expired and get a new one when it is expire. This is where I am looking for support.
Beside that I do have relevant question.
I'm not sure we are in the same situation, but I have followed this tutorial: https://learn.microsoft.com/en-us/graph/tutorials/dotnet?tabs=aad&tutorial-step=3
I struggled to find a way to save user authentication tokens, so the users don't have to authenticate every time my app starts.
Then I found out that DeviceCodeCredentialOptions
has TokenCachePersistenceOptions
. The document clearly says:
Specifies the TokenCachePersistenceOptions to be used by the credential. If not options are specified, the token cache will not be persisted to disk.
There is an example in the document for using TokenCachePersistenceOptions
. However, if you using Microsoft Graph, you need to tweak the code a little bit. Here is the code that works for me:
public static readonly string[] GraphUserScopes = new[]
{
"user.read",
"mail.read",
"mail.send",
"Files.ReadWrite.All",
};
public static async Task SampleRunAsync()
{
var deviceCodeCredentialOptions = new DeviceCodeCredentialOptions()
{
ClientId = ApplicationClientId,
TenantId = DirectoryTenantId,
DeviceCodeCallback = (info, cancle) =>
{
// Display the device code message to
// the user. This tells them
// where to go to sign in and provides the
// code to use.
Console.WriteLine(info.Message);
return Task.FromResult(0);
},
TokenCachePersistenceOptions = new TokenCachePersistenceOptions() {Name = TokenName}
};
DeviceCodeCredential deviceCodeCredential;
if (File.Exists(TokenFp))
{
using var fileStream = new FileStream(TokenFp, FileMode.Open, FileAccess.Read);
deviceCodeCredentialOptions.AuthenticationRecord = await AuthenticationRecord.DeserializeAsync(fileStream).ConfigureAwait(Program.DebugMode);
deviceCodeCredential = new DeviceCodeCredential(deviceCodeCredentialOptions);
}
else
{
deviceCodeCredential = new DeviceCodeCredential(deviceCodeCredentialOptions);
var authenticationRecord = await deviceCodeCredential.AuthenticateAsync(new TokenRequestContext(GraphUserScopes)).ConfigureAwait(Program.DebugMode);
//
using var fileStream1 = new FileStream(TokenFp, FileMode.Create, FileAccess.Write);
await authenticationRecord.SerializeAsync(fileStream1).ConfigureAwait(Program.DebugMode);
}
var graphServiceClient = new GraphServiceClient(deviceCodeCredential, GraphUserScopes);
var user = await graphServiceClient.Me.GetAsync((config) =>
{
// Only request specific properties
config.QueryParameters.Select = new[] {"displayName", "mail", "userPrincipalName"};
}).ConfigureAwait(false);
Console.WriteLine($"Hello, {user?.DisplayName}!");
// For Work/school accounts, email is in Mail property
// Personal accounts, email is in UserPrincipalName
Console.WriteLine($"Email: {user?.Mail ?? user?.UserPrincipalName ?? ""}");
}