CSP Development Recommendations
Designing a system to manage resources typically done through Partner Center can be a complex task based on the requirements for the application being developed. If we were to consider a portal that provides the ability to manage customers, subscriptions, and users it is likely that the portal will leverage Microsoft Graph, Partner Center API, and various other APIs in order to provide a complete solution. All of these components present a challenge for the developers in that they need monitor the health of each call made, and ensure that the credentials and keys utilized to access these APIs are protected. Fortunately there is two Azure services that any developer can leverage in order to obtain the insight needed to ensure their application is healthy and ensure that the credentials and keys are stored in a secure manner.
Application Insights can be utilized to monitor the health of a live application. It will automatically detect performance anomalies, and provide powerful analytics to help you diagnose issues and understand what end-users are actually doing with your website. While you can add this service to any web application without changing any code I would recommend that you add custom events to obtain additional insights. If we were to consider the call the Partner Center API to obtain a list of subscriptions it might be helpful to know how long it took the request to complete and who actually made the request. The following sample code will show how you can the custom events and metrics
/// <summary>
/// Gets the subscriptions for the specified customer.
/// </summary>
/// <param name="customerId">Identifier for the customer.</param>
/// <returns>A list of subscriptions for the customer.</returns>
/// <exception cref="ArgumentException">
/// <paramref name="customerId"/> is empty or null.
/// </exception>
public async Task<List<Subscription>> GetSubscriptionsAsync(string customerId)
{
CustomerPrincipal principal;
DateTime startTime;
Dictionary<string, double> eventMetrics;
Dictionary<string, string> eventProperties;
Guid correlationId;
IPartner operations;
ResourceCollection<Subscription> subscriptions;
customerId.AssertNotEmpty(nameof(customerId));
try
{
startTime = DateTime.Now;
correlationId = Guid.NewGuid();
operations = await GetAppOperationsAsync(correlationId);
principal = (CustomerPrincipal)HttpContext.Current.User;
if (principal.CustomerId.Equals(service.Configuration.PartnerCenterApplicationTenantId))
{
subscriptions = await operations.Customers.ById(customerId).Subscriptions.GetAsync();
}
else
{
subscriptions = await operations.Customers.ById(principal.CustomerId).Subscriptions.GetAsync();
}
// Track the event measurements for analysis.
eventMetrics = new Dictionary<string, double>
{
{ "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds },
{ "NumberOfSubscriptions", subscriptions.TotalCount }
};
// Capture the request for the customer summary for analysis.
eventProperties = new Dictionary<string, string>
{
{ "CustomerId", principal.CustomerId },
{ "Name", principal.Name },
{ "ParternCenterCorrelationId", correlationId.ToString() }
};
service.Telemetry.TrackEvent("GetSubscriptionsAsync", eventProperties, eventMetrics);
return new List<Subscription>(subscriptions.Items);
}
finally
{
eventMetrics = null;
eventProperties = null;
operations = null;
principal = null;
}
}
The snippet of code above is part of the Partner Center Explorer open source sample project. Through this code the elapsed time, number of subscriptions returned, customer identifier and name of the calling user, and the correlation identifier used when calling the Partner Center API are all capture as a custom event. It is worth mentioning that you can configure a handful of additional headers when interacting with the Partner Center API, as documented here. It is recommended that you specify a unique identifier for calls to the API using the x-ms-correlation-id header so it easier to to tie error responses back to original requests when debugging. Also, you should specify an application name using the MS-PartnerCenter-Application header, so that support can easily identify all requests coming from your application. If you are using the .NET SDK all of this accomplished when you initialize a new instance of the partner service. The follow snippet of code shows how this is accomplished
Guid correlationId = Guid.NewGuid();
IPartnerCredentials credentials = await PartnerCredentials.Instance.GenerateByApplicationCredentialsAsync(
"PartnerCenterApplicationId",
"PartnerCenterApplicationSecret",
"PartnerCenterApplicationTenantId");
IAggregatePartner appOperations = PartnerService.Instance.CreatePartnerOperations(credentials);
IPartner operations = appOperations.With(RequestContextFactory.Instance.Create(correlationId));
PartnerService.Instance.ApplicationName = "Application Name";
Customer customer = await operations.Customers.ById("customerId").GetAsync();
When you run a query against Application Insights for a custom event you will be able to get all the details pertaining to the event. This information can be used to configure alerts or create visualization through Power BI. The figure below show some of the data that will be returned when the GetSubscriptionsAsync function from above is invoked
When you capture telemetry data similar to this it makes it possible for you to determine if the dependent APIs calls are resulting in performance issues for your application. Also, it allows you to get insights into how end-users are using your application. If you would like to learn more about Application Insight then checkout the What is Application Insight? documentation. To learn more about the integration with Power BI then check out Application Insights content pack for Power BI.
Most developers store connection strings, secret keys, and service account credentials in configuration files such as the web.config file. While this approach provides an easy way to consume this sensitive information it does not protect the information in the event the site, or server, were to be compromised. If an authorized individual managed to get a hold of the client identifier and secret key utilized to access the Partner Center API then there is real potential for damage to your customers or fraud. Azure Key Vault is a service that can encrypt keys and secrets (such as authentication keys, storage account keys, data encryption keys, .PFX files, and passwords) by using keys that are protected by hardware security modules (HSMs).
It is recommended that you leverage an instance of Key Vault to store connection strings, secret keys, and service account credentials to ensure they are protected. In order to access secrets stored in an instance of Key Vault you will create the appropriate security principal and grant it the appropriate privileges. This ensure that the authenticated entity can only perform the operations that you select and it provides a way to audit who, or what, has interacted with the privileged information. Since connection strings and secret keys may need to be utilized prior to prompting a user for credentials I recommend that you grant an Azure AD application the ability to get information from Key Vault. You can authenticate an Azure AD application using a client identifier and secret key or with a client identifier and a certificate. My recommendation would be to utilize a certificate, so that you do not have to store any secret keys in any configuration files. You can read more about this at Authenticating with a Certificate instead of Client Secret, and if you want step by step direction on to configure this please check out https://github.com/Microsoft/Partner-Center-Explorer/blob/master/docs/KeyVault.md.
You can find an example of how this approach to securing privileged information at Configuration.cs and VaultService.cs.