Table of Contents

Authenticate with Azure Container Registry

using OrasProject.Oras.Registry.Remote.Auth;
using Azure.Identity;
using Azure.Core;
using Azure.Containers.ContainerRegistry;

namespace OrasProject.Oras.Examples.Azure;

// This is an example implementation of AzureCredentialProvider, which
// can be used to authenticate with Azure Container Registry. It uses
// DefaultAzureCredential with the fully qualified type name. It implements 
// the ICredentialProvider interface.
public class AzureCredentialProvider(string host) : ICredentialProvider
{
    public string Host { get; } = host;
    private string _aadToken { get; set; } = string.Empty;
    private Credential _credential { get; set; } = new Credential();
    private DateTimeOffset _tokenExpiry { get; set; } = DateTimeOffset.MinValue;
    private readonly TokenCredential _aadCredential = new DefaultAzureCredential();
    private ContainerRegistryClient _acrClient { get; set; } = new ContainerRegistryClient(new Uri($"https://{host}"));

    private async Task<string> GetAadTokenAsync(CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(_aadToken) || DateTimeOffset.UtcNow >= _tokenExpiry)
        {
            string[] scopes = ["https://containerregistry.azure.net"];
            var token = await _aadCredential.GetTokenAsync(new TokenRequestContext(scopes), cancellationToken).ConfigureAwait(false);
            _aadToken = token.Token;
            _tokenExpiry = token.ExpiresOn;
        }
        return _aadToken;
    }

    // Implement the ICredentialProvider interface.
    public async Task<Credential> ResolveCredentialAsync(string hostname, CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(hostname))
        {
            throw new ArgumentException("Hostname cannot be null or empty.", nameof(hostname));
        }

        if (hostname != Host)
        {
            throw new ArgumentException($"Hostname '{hostname}' does not match the expected host '{Host}'.", nameof(hostname));
        }

        if (!_credential.IsEmpty() && DateTimeOffset.UtcNow < _tokenExpiry)
        {
            return _credential;
        }

        var aadToken = await GetAadTokenAsync(cancellationToken).ConfigureAwait(false);
        var response = await _acrClient.ExchangeAadAccessTokenForAcrRefreshTokenAsync(hostname, null, null, aadToken, cancellationToken);
        _credential = new Credential(RefreshToken: response.Value.RefreshToken);
        return _credential;
    }
}
using OrasProject.Oras.Registry.Remote.Auth;
using OrasProject.Oras.Registry.Remote;
using OrasProject.Oras.Registry;

namespace OrasProject.Oras.Examples.Azure;

public static class AzureContainerRegistryAuthentication
{
    // This example demonstrates how to use the ICredentialProvider interface to
    // authenticate with Azure Container Registry, and perform a copy operation
    // between two ACR repositories.
    // For production use: Implement proper exception handling, cancellation, and dependency injection.
    public static async Task AuthenticateWithAzureContainerRegistryAsync(CancellationToken cancellationToken = default)
    {
        using var httpClient = new HttpClient();

        // Create a repository instance for the source repository.
        var srcCredentialProvider = new AzureCredentialProvider("mysourceregistry.azurecr.io");
        var srcRepository = new Repository(new RepositoryOptions
        {
            Reference = Reference.Parse("mysourceregistry.azurecr.io/myimage"),
            Client = new Client(httpClient, credentialProvider: srcCredentialProvider),
        });

        // Create a repository instance for the destination repository.
        var dstCredentialProvider = new AzureCredentialProvider("mydestinationregistry.azurecr.io");
        var dstRepository = new Repository(new RepositoryOptions
        {
            Reference = Reference.Parse("mydestinationregistry.azurecr.io/myimage"),
            Client = new Client(httpClient, credentialProvider: dstCredentialProvider),
        });

        // Copy the artifact tagged by v1.1.0 from the source repository to the destination
        // repository, and tag it with v1.
        var srcReference = "v1.1.0";
        var dstReference = "v1";
        await srcRepository.CopyAsync(srcReference, dstRepository, dstReference, cancellationToken);
    }
}