Skip to content

Commit

Permalink
CDMS-181 provides the TokenCredential by using ConfidentialClientAppl…
Browse files Browse the repository at this point in the history
…icationBuilder
  • Loading branch information
craigedmunds committed Dec 9, 2024
1 parent a22c435 commit a40e810
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 14 deletions.
15 changes: 5 additions & 10 deletions Btms.Azure/AzureService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
using Azure.Core.Pipeline;
using Azure.Identity;
using Btms.Azure.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Client;

namespace Btms.Azure;


public abstract class AzureService
{
protected readonly TokenCredential Credentials;
protected readonly HttpClientTransport? Transport;
protected readonly ILogger Logger;

protected AzureService(ILogger logger, IAzureConfig config, IHttpClientFactory? clientFactory = null)
protected AzureService(IServiceProvider serviceProvider, ILogger logger, IAzureConfig config, IHttpClientFactory? clientFactory = null)
{
Logger = logger;
using AzureEventSourceListener listener = AzureEventSourceListener.CreateConsoleLogger(EventLevel.Verbose);
Expand All @@ -25,16 +27,9 @@ protected AzureService(ILogger logger, IAzureConfig config, IHttpClientFactory?
logger.LogDebug("Creating azure credentials based on config vars for {AzureClientId}",
config.AzureClientId);

var httpClientFactory = serviceProvider.GetRequiredService<MsalHttpClientFactoryAdapter>();

var app = PublicClientApplicationBuilder.Create("<yourClientId>")
// [...] Your MSAL app setup
.WithHttpClientFactory(httpClientFactory)
.Build();

Credentials =
new ClientSecretCredential(config.AzureTenantId, config.AzureClientId, config.AzureClientSecret);

new ConfidentialClientApplicationTokenCredential(serviceProvider, config);
logger.LogDebug("Created azure credentials");
}
else
Expand Down
45 changes: 45 additions & 0 deletions Btms.Azure/ConfidentialClientApplicationTokenCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Azure.Core;
using Btms.Azure.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;

namespace Btms.Azure;

/// <summary>
/// Takes care of retriving a token via ConfidentialClientApplicationBuilder
/// which allows us to inject our CDP_HTTPS_PROXY based http client.
///
/// It's unclear why this isn't available out of the box!
/// - IMsalHttpClientFactory isn't used by ClientSecretCredential
/// - The ClientSecretCredential has an internal constructor accepting MsalConfidentialClient but nothing seems to use it
/// - MsalConfidentialClient is itself internal
/// </summary>
/// <param name="token"></param>
/// <param name="expiresOn"></param>
public class ConfidentialClientApplicationTokenCredential : TokenCredential
{
private readonly string[] _scopes = { "https://storage.azure.com/.default" };

private readonly IConfidentialClientApplication _app;
public ConfidentialClientApplicationTokenCredential(IServiceProvider serviceProvider, IAzureConfig config)
{
var httpClientFactory = serviceProvider.GetRequiredService<MsalHttpClientFactoryAdapter>();

_app = ConfidentialClientApplicationBuilder.Create(config.AzureClientId)
.WithHttpClientFactory(httpClientFactory)
.WithTenantId(config.AzureTenantId)
.WithClientSecret(config.AzureClientSecret)
.Build();
}
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
var authResult = _app.AcquireTokenForClient(_scopes).ExecuteAsync(cancellationToken).Result;
return ValueTask.FromResult(new AccessToken(authResult.AccessToken, authResult.ExpiresOn));
}

public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
var authResult = _app.AcquireTokenForClient(_scopes).ExecuteAsync(cancellationToken).Result;
return new AccessToken(authResult.AccessToken, authResult.ExpiresOn);
}
}
2 changes: 1 addition & 1 deletion Btms.Azure/Extensions/IServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Btms.Azure.Extensions;
/// <summary>
/// The Azure SDK doesn't use the CDP proxied Http Client by default, so previously we used the HTTPS_CLIENT env var to
/// send the requests via CDPs squid proxy. This code is intended to use the http client we already setup that uses the proxy
/// when the CDP_HTTPS_PROXY is set.
/// when the CDP_HTTPS_PROXY env var is set.
/// Code borrowed from https://anthonysimmon.com/overriding-msal-httpclient-with-ihttpclientfactory/
/// </summary>
/// <param name="httpClientFactory"></param>
Expand Down
3 changes: 2 additions & 1 deletion Btms.Backend/Config/ApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class ApiOptions
// This is used by the azure library when connecting to auth related services
// when connecting to blob storage
[ConfigurationKeyName("HTTPS_PROXY")]
public string? HttpsProxy => CdpHttpsProxy?.Contains("://") == true ? CdpHttpsProxy[(CdpHttpsProxy.IndexOf("://", StringComparison.Ordinal) + 3)..] : null;
public string? HttpsProxy { get; set; }
// public string? HttpsProxy => CdpHttpsProxy?.Contains("://") == true ? CdpHttpsProxy[(CdpHttpsProxy.IndexOf("://", StringComparison.Ordinal) + 3)..] : null;

public Dictionary<string, string?> Credentials { get; set; } = [];

Expand Down
3 changes: 2 additions & 1 deletion Btms.BlobService/BlobService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
namespace Btms.BlobService;

public class BlobService(
IServiceProvider serviceProvider,
IBlobServiceClientFactory blobServiceClientFactory,
ILogger<BlobService> logger,
IOptions<BlobServiceOptions> options,
IHttpClientFactory clientFactory)
: AzureService(logger, options.Value, clientFactory), IBlobService
: AzureService(serviceProvider, logger, options.Value, clientFactory), IBlobService
{
private BlobContainerClient CreateBlobClient(int timeout = default, int retries = default)
{
Expand Down
3 changes: 2 additions & 1 deletion Btms.BlobService/BlobServiceClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
namespace Btms.BlobService;

public class BlobServiceClientFactory(
IServiceProvider serviceProvider,
IOptions<BlobServiceOptions> options,
ILogger<BlobServiceClientFactory> logger,
IHttpClientFactory? clientFactory = null)
: AzureService(logger, options.Value, clientFactory), IBlobServiceClientFactory
: AzureService(serviceProvider, logger, options.Value, clientFactory), IBlobServiceClientFactory
{
public BlobServiceClient CreateBlobServiceClient(int timeout = default, int retries = default)
{
Expand Down

0 comments on commit a40e810

Please sign in to comment.