PPDS.Auth
December 29, 2025 ยท View on GitHub
Authentication profile management for Power Platform with encrypted credential storage and multi-cloud support.
Installation
dotnet add package PPDS.Auth
Quick Start
// 1. Create and store a profile
var store = new ProfileStore();
var profile = new AuthProfile("dev")
{
AuthMethod = AuthMethod.InteractiveBrowser,
ClientId = "51f81489-12ee-4a9e-aaae-a2591f45987d" // Default Power Platform client
};
await store.SaveAsync(profile);
// 2. Authenticate and get a ServiceClient
var provider = CredentialProviderFactory.Create(profile);
var client = await ServiceClientFactory.CreateAsync(provider, profile);
Features
Profile Storage
Profiles are stored with encrypted secrets (DPAPI on Windows):
var store = new ProfileStore();
// Save a profile
await store.SaveAsync(profile);
// Load all profiles
var profiles = await store.LoadAsync();
// Get active profile
var active = profiles.GetActiveProfile();
// Set active profile
profiles.SetActive("dev");
await store.SaveAsync(profiles);
Default storage location: ~/.ppds/profiles.json
Authentication Methods
// Interactive Browser (opens browser for OAuth)
var profile = new AuthProfile("dev")
{
AuthMethod = AuthMethod.InteractiveBrowser,
ClientId = "51f81489-12ee-4a9e-aaae-a2591f45987d"
};
// Device Code (for headless/SSH environments)
var profile = new AuthProfile("dev")
{
AuthMethod = AuthMethod.DeviceCode,
ClientId = "51f81489-12ee-4a9e-aaae-a2591f45987d"
};
// Client Secret (service principal)
var profile = new AuthProfile("ci")
{
AuthMethod = AuthMethod.ClientSecret,
ApplicationId = "your-app-id",
TenantId = "your-tenant-id",
ClientSecret = "your-secret" // Stored encrypted
};
// Certificate File
var profile = new AuthProfile("prod")
{
AuthMethod = AuthMethod.CertificateFile,
ApplicationId = "your-app-id",
TenantId = "your-tenant-id",
CertificatePath = "/path/to/cert.pfx",
CertificatePassword = "password" // Stored encrypted
};
// Certificate Store (Windows)
var profile = new AuthProfile("prod")
{
AuthMethod = AuthMethod.CertificateStore,
ApplicationId = "your-app-id",
TenantId = "your-tenant-id",
CertificateThumbprint = "ABC123...",
CertificateStoreLocation = StoreLocation.CurrentUser
};
// Managed Identity (Azure-hosted)
var profile = new AuthProfile("azure")
{
AuthMethod = AuthMethod.ManagedIdentity,
ManagedIdentityClientId = "user-assigned-id" // Optional for user-assigned
};
// GitHub Actions OIDC
var profile = new AuthProfile("github")
{
AuthMethod = AuthMethod.GitHubFederated,
ApplicationId = "your-app-id",
TenantId = "your-tenant-id"
};
// Azure DevOps OIDC
var profile = new AuthProfile("azdo")
{
AuthMethod = AuthMethod.AzureDevOpsFederated,
ApplicationId = "your-app-id",
TenantId = "your-tenant-id",
AzureDevOpsServiceConnectionId = "service-connection-id"
};
Environment Discovery
Discover environments accessible to the authenticated user:
var provider = CredentialProviderFactory.Create(profile);
var discovery = new GlobalDiscoveryService();
var environments = await discovery.GetEnvironmentsAsync(provider.GetTokenAsync);
foreach (var env in environments)
{
Console.WriteLine($"{env.FriendlyName} - {env.Url}");
}
Environment Resolution
Resolve environments by various identifiers:
var resolver = new EnvironmentResolver(discovery, provider.GetTokenAsync);
// By URL
var env = await resolver.ResolveAsync("https://org.crm.dynamics.com");
// By unique name
var env = await resolver.ResolveAsync("org");
// By environment ID
var env = await resolver.ResolveAsync("00000000-0000-0000-0000-000000000000");
// By partial friendly name (fuzzy match)
var env = await resolver.ResolveAsync("Production");
Multi-Cloud Support
var profile = new AuthProfile("gcc")
{
Cloud = CloudEnvironment.UsGov, // GCC
// ... other settings
};
// Supported clouds:
// - CloudEnvironment.Public (default)
// - CloudEnvironment.UsGov (GCC)
// - CloudEnvironment.UsGovHigh (GCC High)
// - CloudEnvironment.UsGovDoD (DoD)
// - CloudEnvironment.China
// - CloudEnvironment.UsNat
// - CloudEnvironment.UsSec
Integration with PPDS.Dataverse
Use profiles as a connection source for the connection pool:
var store = new ProfileStore();
var profiles = await store.LoadAsync();
var profile = profiles.GetActiveProfile();
var provider = CredentialProviderFactory.Create(profile);
var source = new ProfileConnectionSource(profile, provider);
services.AddDataverseConnectionPool(options =>
{
options.ConnectionSources.Add(source);
});
Custom Credential Providers
Implement ICredentialProvider for custom authentication:
public class CustomCredentialProvider : ICredentialProvider
{
public string Name => "Custom";
public bool RequiresInteraction => false;
public Task<string> GetTokenAsync(string resource, CancellationToken ct)
{
// Your custom token acquisition logic
return Task.FromResult("your-token");
}
public Task<TokenInfo> GetTokenInfoAsync(CancellationToken ct)
{
return Task.FromResult(new TokenInfo
{
UserPrincipalName = "user@example.com",
TenantId = "..."
});
}
}
Security
Credential Encryption
Secrets (client secrets, passwords, etc.) are encrypted at rest:
- Windows: DPAPI (user-scoped encryption)
- Linux/macOS: Base64 encoding (use OS-level file permissions)
Token Caching
MSAL token caching is used for interactive and device code flows:
var profile = new AuthProfile("dev")
{
TokenCacheType = TokenCacheType.Persistent // Default
// TokenCacheType = TokenCacheType.InMemory // Per-process only
};
Target Frameworks
net8.0net9.0net10.0
License
MIT License