Connecting to Dynamics 365
Introduction
Flux is built atop Dynamics, necessitating a connection with Dynamics for its operations. Depending on Dynamics' authentication configuration, Flux provides multiple connection methods.
1. Connection using Connection String
The most straightforward connection method involves supplying a connection string during the Flux server's initialization. Here's an example:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddFluxServer(options => {
options.ConnectionString = "<Replace_Connection_String>"; // Connection to Dynamics
});
2. Connection Handling with ADFS Token
There are instances where Dynamics' authentication configurations, like Windows authentication, aren't supported by the Microsoft.PowerPlatform.Dataverse.Client library used by Flux. For these cases, Flux offers more granular control over how the connection to Dynamics is established.
Below is a sample code that retrieves an access token from ADFS and uses it to initialize a new ServiceClient instance:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<TokenManagerOptions>()
.BindConfiguration("adfsAppSettings");
builder.Services
.AddSingleton<TokenManager>()
.AddFluxServer(sp => new ServiceClient(new Uri(builder.Configuration["dataverseUrl"]),
uri => sp.GetRequiredService<TokenManager>().AcquireToken()));
Token Manager Implementation
In this example, the TokenManager class is responsible for provisioning a token from Active Directory Federation Services (ADFS). The TokenManager ensures the token remains refreshed, especially as it nears expiration.
using IdentityModel.Client;
using Microsoft.Extensions.Options;
internal sealed class TokenManager {
private readonly TokenManagerOptions options;
private TokenResponse token;
public TokenManager(IOptions<TokenManagerOptions> options) {
if (options is null) {
throw new ArgumentNullException(nameof(options));
}
this.options = options.Value;
}
public async Task<string> AcquireToken() {
if (this.token == null || this.token.IsError || this.token.ExpiresIn < DateTime.Now.AddMinutes(-2).Ticks) {
var tokenClient = new TokenClient(
new HttpClient(),
new TokenClientOptions() {
Address = this.options.Address,
ClientId = this.options.ClientId,
ClientSecret = this.options.ClientSecret,
Parameters = new Parameters() {
new KeyValuePair<string, string>("resource", this.options.Resource)
}
}
);
if (this.token == null || this.token.IsError) {
this.token = await tokenClient.RequestPasswordTokenAsync(this.options.Username, this.options.Password).ConfigureAwait(false);
} else { // renew token
this.token = await tokenClient.RequestRefreshTokenAsync(this.token.RefreshToken).ConfigureAwait(false);
}
if (!this.token.IsError) {
return this.token.AccessToken;
} else if (this.token.Exception != null) {
throw this.token.Exception;
} else if (this.token.Error != null) {
throw new InvalidOperationException($"Error: {this.token.Error} - Description: {this.token.ErrorDescription}");
} else {
throw new InvalidOperationException("Unspecified error while acquiring token.");
}
} else {
return this.token.AccessToken;
}
}
}
3. Connection using User's Security Context
In scenarios necessitating Dynamics connections through a user's security context (a valid Dynamics user), the Microsoft.PowerPlatform.Dataverse.Client.IOrganizationServiceAsync creation per user is crucial. Even in this configuration, you must initially set up the Flux server to connect to Dynamics, as previously described.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<TokenManagerOptions>()
.BindConfiguration("adfsAppSettings");
builder.Services
.AddSingleton<TokenManager>()
.AddFluxServer(sp => new ServiceClient(new Uri(builder.Configuration["dataverseUrl"]),
uri => sp.GetRequiredService<TokenManager>().AcquireToken()))
.AddUserResolver<UserResolver>(sp => new ServiceClient(new Uri(builder.Configuration["dataverseUrl"]),
uri => sp.GetAccessToken()));
Retrieving the User Token
The following code demonstrates how to configure Flux to connect to Dynamics using the security context of the logged-in user. This approach is achieved by specifying the provisioning of IOrganizationServiceAsync within the AddUserResolver method. The user token is extracted from the authorization header and subsequently used to instantiate the IOrganizationServiceAsync:
internal static Task<string> GetAccessToken(this IServiceProvider sp) {
var context = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (context.Request.Headers.TryGetValue("Authorization", out var authorizationHeader)) {
if (authorizationHeader.Count == 1) {
if (authorizationHeader[0].StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) {
return Task.FromResult(authorizationHeader[0].Substring("Bearer ".Length).Trim());
}
}
}
return Task.FromResult<string>(default);
}