Dynamics 365 Authorisation Code Flow
Post by: syed hussain in All Dynamics 365 CE
Summary
In this flow, after the user successfully authenticates (through an interactive login prompt), an authorization code is issued by Azure AD. This code is then exchanged for an access token that can be used to make API calls.
Code Snippet
MSAL simplifies handling of this flow, managing login, token caching, token refreshing, and authentication.It manages PKCE (Proof Key for Code Exchange) for you, adding additional security to the Authorization Code Flow.
using Microsoft.Identity.Client;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.IO;
using System.Linq; // LINQ for FirstOrDefault and Any
using System.Net.Http;
using System.Threading.Tasks;
internal class Program
{
private static string clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d"; // Pre-registered app ID
private static string tenantId = "common"; // Use "common" for multi-tenant or specific tenant ID
private static string[] scopes = new string[] { "https://eax.crm11.dynamics.com/.default" };
private static string authority = $"https://login.microsoftonline.com/{tenantId}";
private static string cacheFile = "msal_token_cache.bin";
private static async Task Main(string[] args)
{
try
{
var pca = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri("http://localhost")
.Build();
// Load cache from file (persistent cache)
var tokenCache = pca.UserTokenCache;
var cacheHelper = new TokenCacheHelper(cacheFile);
tokenCache.SetBeforeAccess(cacheHelper.BeforeAccessNotification);
tokenCache.SetAfterAccess(cacheHelper.AfterAccessNotification);
AuthenticationResult result;
var accounts = await pca.GetAccountsAsync();
if (accounts.Any()) // Use .Any() instead of .Length
{
// Try to acquire token silently from cache
try
{
result = await pca.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync(); // Use .FirstOrDefault()
}
catch (MsalUiRequiredException)
{
// Fallback to interactive login if token is not found in cache
result = await pca.AcquireTokenInteractive(scopes).ExecuteAsync();
}
}
else
{
// First time interactive login
result = await pca.AcquireTokenInteractive(scopes).ExecuteAsync();
}
Console.WriteLine("Access Token: " + result.AccessToken);
// Use the token for an API call (e.g., to retrieve the Contact SystemForm)
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
// Query for Contact SystemForm by filtering the objecttypecode (Contact's
// objecttypecode is usually 2)
string query = "https://eax.crm11.dynamics.com/api/data/v9.0/systemforms?$filter=objecttypecode eq 'contact' and name eq 'Information'";
var response = await client.GetAsync(query);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine("Response: " + content);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
// Helper class to handle caching in file
public class TokenCacheHelper
{
private readonly string CacheFilePath;
public TokenCacheHelper(string filePath)
{
CacheFilePath = filePath;
}
public void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
if (File.Exists(CacheFilePath))
{
var data = File.ReadAllBytes(CacheFilePath);
args.TokenCache.DeserializeMsalV3(data);
}
}
public void AfterAccessNotification(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
var data = args.TokenCache.SerializeMsalV3();
File.WriteAllBytes(CacheFilePath, data);
}
}
}