{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"dataverse-python-authentication-security","version":"0.1.0"},"spec":{"agents_md":"---\napplyTo: '**'\n---\n\n# Dataverse SDK for Python — Authentication \u0026 Security Patterns\n\nBased on official Microsoft Azure SDK authentication documentation and Dataverse SDK best practices.\n\n## 1. Authentication Overview\n\nThe Dataverse SDK for Python uses Azure Identity credentials for token-based authentication. This approach follows the principle of least privilege and works across local development, cloud deployment, and on-premises environments.\n\n### Why Token-Based Authentication?\n\n**Advantages over connection strings**:\n- Establishes specific permissions needed by your app (principle of least privilege)\n- Credentials are scoped only to intended apps\n- With managed identity, no secrets to store or compromise\n- Works seamlessly across environments without code changes\n\n---\n\n## 2. Credential Types \u0026 Selection\n\n### Interactive Browser Credential (Local Development)\n\n**Use for**: Developer workstations during local development.\n\n```python\nfrom azure.identity import InteractiveBrowserCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# Opens browser for authentication\ncredential = InteractiveBrowserCredential()\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n\n# First use prompts for sign-in; subsequent calls use cached token\nrecords = client.get(\"account\")\n```\n\n**When to use**:\n- ✅ Interactive development and testing\n- ✅ Desktop applications with UI\n- ❌ Background services or scheduled jobs\n\n---\n\n### Default Azure Credential (Recommended for All Environments)\n\n**Use for**: Apps that run in multiple environments (dev → test → production).\n\n```python\nfrom azure.identity import DefaultAzureCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# Attempts credentials in this order:\n# 1. Environment variables (app service principal)\n# 2. Azure CLI credentials (local development)\n# 3. Azure PowerShell credentials (local development)\n# 4. Managed identity (when running in Azure)\ncredential = DefaultAzureCredential()\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n\nrecords = client.get(\"account\")\n```\n\n**Advantages**:\n- Single code path works everywhere\n- No environment-specific logic needed\n- Automatically detects available credentials\n- Preferred for production apps\n\n**Credential chain**:\n1. Environment variables (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`)\n2. Visual Studio Code login\n3. Azure CLI (`az login`)\n4. Azure PowerShell (`Connect-AzAccount`)\n5. Managed identity (on Azure VMs, App Service, AKS, etc.)\n\n---\n\n### Client Secret Credential (Service Principal)\n\n**Use for**: Unattended authentication (scheduled jobs, scripts, on-premises services).\n\n```python\nfrom azure.identity import ClientSecretCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\nimport os\n\ncredential = ClientSecretCredential(\n    tenant_id=os.environ[\"AZURE_TENANT_ID\"],\n    client_id=os.environ[\"AZURE_CLIENT_ID\"],\n    client_secret=os.environ[\"AZURE_CLIENT_SECRET\"]\n)\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n\nrecords = client.get(\"account\")\n```\n\n**Setup steps**:\n1. Create app registration in Azure AD\n2. Create client secret (keep secure!)\n3. Grant Dataverse permissions to the app\n4. Store credentials in environment variables or secure vault\n\n**Security concerns**:\n- ⚠️ Never hardcode credentials in source code\n- ⚠️ Store secrets in Azure Key Vault or environment variables\n- ⚠️ Rotate credentials regularly\n- ⚠️ Use minimal required permissions\n\n---\n\n### Managed Identity Credential (Azure Resources)\n\n**Use for**: Apps hosted in Azure (App Service, Azure Functions, AKS, VMs).\n\n```python\nfrom azure.identity import ManagedIdentityCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# No secrets needed - Azure manages identity\ncredential = ManagedIdentityCredential()\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n\nrecords = client.get(\"account\")\n```\n\n**Benefits**:\n- ✅ No secrets to manage\n- ✅ Automatic token refresh\n- ✅ Highly secure\n- ✅ Built-in to Azure services\n\n**Setup**:\n1. Enable managed identity on Azure resource (App Service, VM, etc.)\n2. Grant Dataverse permissions to the managed identity\n3. Code automatically uses the identity\n\n---\n\n## 3. Environment-Specific Configuration\n\n### Local Development\n\n```python\n# .env file (git-ignored)\nDATAVERSE_URL=https://myorg-dev.crm.dynamics.com\n\n# Python code\nimport os\nfrom azure.identity import DefaultAzureCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# Uses your Azure CLI credentials\ncredential = DefaultAzureCredential()\nclient = DataverseClient(\n    base_url=os.environ[\"DATAVERSE_URL\"],\n    credential=credential\n)\n```\n\n**Setup**: `az login` with your developer account\n\n---\n\n### Azure App Service / Azure Functions\n\n```python\nfrom azure.identity import ManagedIdentityCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# Automatically uses managed identity\ncredential = ManagedIdentityCredential()\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n```\n\n**Setup**: Enable managed identity in App Service, grant permissions in Dataverse\n\n---\n\n### On-Premises / Third-Party Hosting\n\n```python\nimport os\nfrom azure.identity import ClientSecretCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\ncredential = ClientSecretCredential(\n    tenant_id=os.environ[\"AZURE_TENANT_ID\"],\n    client_id=os.environ[\"AZURE_CLIENT_ID\"],\n    client_secret=os.environ[\"AZURE_CLIENT_SECRET\"]\n)\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential\n)\n```\n\n**Setup**: Create service principal, store credentials securely, grant Dataverse permissions\n\n---\n\n## 4. Client Configuration \u0026 Connection Settings\n\n### Basic Configuration\n\n```python\nfrom PowerPlatform.Dataverse.core.config import DataverseConfig\nfrom azure.identity import DefaultAzureCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\ncfg = DataverseConfig()\ncfg.logging_enable = True  # Enable detailed logging\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=DefaultAzureCredential(),\n    config=cfg\n)\n```\n\n### HTTP Tuning\n\n```python\nfrom PowerPlatform.Dataverse.core.config import DataverseConfig\n\ncfg = DataverseConfig()\n\n# Timeout settings\ncfg.http_timeout = 30          # Request timeout in seconds\n\n# Retry configuration\ncfg.http_retries = 3           # Number of retry attempts\ncfg.http_backoff = 1           # Initial backoff in seconds\n\n# Connection reuse\ncfg.connection_timeout = 5     # Connection timeout\n\nclient = DataverseClient(\n    base_url=\"https://myorg.crm.dynamics.com\",\n    credential=credential,\n    config=cfg\n)\n```\n\n---\n\n## 5. Security Best Practices\n\n### 1. Never Hardcode Credentials\n\n```python\n# ❌ BAD - Don't do this!\ncredential = ClientSecretCredential(\n    tenant_id=\"your-tenant-id\",\n    client_id=\"your-client-id\",\n    client_secret=\"your-secret-key\"  # EXPOSED!\n)\n\n# ✅ GOOD - Use environment variables\nimport os\ncredential = ClientSecretCredential(\n    tenant_id=os.environ[\"AZURE_TENANT_ID\"],\n    client_id=os.environ[\"AZURE_CLIENT_ID\"],\n    client_secret=os.environ[\"AZURE_CLIENT_SECRET\"]\n)\n```\n\n### 2. Store Secrets Securely\n\n**Development**:\n```bash\n# .env file (git-ignored)\nAZURE_TENANT_ID=your-tenant-id\nAZURE_CLIENT_ID=your-client-id\nAZURE_CLIENT_SECRET=your-secret-key\n```\n\n**Production**:\n```python\nfrom azure.keyvault.secrets import SecretClient\nfrom azure.identity import DefaultAzureCredential\n\n# Retrieve secrets from Azure Key Vault\ncredential = DefaultAzureCredential()\nclient = SecretClient(\n    vault_url=\"https://mykeyvault.vault.azure.net\",\n    credential=credential\n)\n\nsecret = client.get_secret(\"dataverse-client-secret\")\n```\n\n### 3. Implement Principle of Least Privilege\n\n```python\n# Grant minimal permissions:\n# - Only read if app only reads\n# - Only specific tables if possible\n# - Time-limit credentials (auto-rotation)\n# - Use managed identity instead of shared secrets\n```\n\n### 4. Monitor Authentication Events\n\n```python\nimport logging\n\nlogger = logging.getLogger(\"dataverse_auth\")\n\ntry:\n    client = DataverseClient(\n        base_url=\"https://myorg.crm.dynamics.com\",\n        credential=credential\n    )\n    logger.info(\"Successfully authenticated to Dataverse\")\nexcept Exception as e:\n    logger.error(f\"Authentication failed: {e}\")\n    raise\n```\n\n### 5. Handle Token Expiration\n\n```python\nfrom azure.core.exceptions import ClientAuthenticationError\nimport time\n\ndef create_with_auth_retry(client, table_name, payload, max_retries=2):\n    \"\"\"Create record, retrying if token expired.\"\"\"\n    for attempt in range(max_retries):\n        try:\n            return client.create(table_name, payload)\n        except ClientAuthenticationError:\n            if attempt \u003c max_retries - 1:\n                logger.warning(\"Token expired, retrying...\")\n                time.sleep(1)\n            else:\n                raise\n```\n\n---\n\n## 6. Multi-Tenant Applications\n\n### Tenant-Aware Client\n\n```python\nfrom azure.identity import DefaultAzureCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\ndef get_client_for_tenant(tenant_id: str) -\u003e DataverseClient:\n    \"\"\"Get DataverseClient for specific tenant.\"\"\"\n    credential = DefaultAzureCredential()\n    \n    # Dataverse URL contains tenant-specific org\n    base_url = f\"https://{get_org_for_tenant(tenant_id)}.crm.dynamics.com\"\n    \n    return DataverseClient(\n        base_url=base_url,\n        credential=credential\n    )\n\ndef get_org_for_tenant(tenant_id: str) -\u003e str:\n    \"\"\"Map tenant to Dataverse organization.\"\"\"\n    # Implementation depends on your multi-tenant strategy\n    # Could be database lookup, configuration, etc.\n    pass\n```\n\n---\n\n## 7. Troubleshooting Authentication\n\n### Error: \"Access Denied\" (403)\n\n```python\ntry:\n    client.get(\"account\")\nexcept DataverseError as e:\n    if e.status_code == 403:\n        print(\"User/app lacks Dataverse permissions\")\n        print(\"Ensure Dataverse security role is assigned\")\n```\n\n### Error: \"Invalid Credentials\" (401)\n\n```python\n# Check credential source\nfrom azure.identity import DefaultAzureCredential\n\ntry:\n    cred = DefaultAzureCredential(exclude_cli_credential=False, \n                                  exclude_powershell_credential=False)\n    # Force re-authentication\n    import subprocess\n    subprocess.run([\"az\", \"login\"])\nexcept Exception as e:\n    print(f\"Authentication failed: {e}\")\n```\n\n### Error: \"Invalid Tenant\" \n\n```python\n# Verify tenant ID\nimport json\nfrom azure.identity import DefaultAzureCredential\n\ncredential = DefaultAzureCredential()\ntoken = credential.get_token(\"https://dataverse.dynamics.com/.default\")\n\n# Decode token to verify tenant\nimport base64\npayload = base64.b64decode(token.token.split('.')[1] + '==')\nclaims = json.loads(payload)\nprint(f\"Token tenant: {claims.get('tid')}\")\n```\n\n---\n\n## 8. Credential Lifecycle\n\n### Token Refresh\n\nAzure Identity handles token refresh automatically:\n\n```python\n# Tokens are cached and refreshed automatically\ncredential = DefaultAzureCredential()\n\n# First call acquires token\nclient.get(\"account\")\n\n# Subsequent calls reuse cached token\nclient.get(\"contact\")\n\n# If token expires, SDK automatically refreshes\n```\n\n### Session Management\n\n```python\nclass DataverseSession:\n    \"\"\"Manages DataverseClient lifecycle.\"\"\"\n    \n    def __init__(self, base_url: str):\n        from azure.identity import DefaultAzureCredential\n        \n        self.client = DataverseClient(\n            base_url=base_url,\n            credential=DefaultAzureCredential()\n        )\n    \n    def __enter__(self):\n        return self.client\n    \n    def __exit__(self, exc_type, exc_val, exc_tb):\n        # Cleanup if needed\n        pass\n\n# Usage\nwith DataverseSession(\"https://myorg.crm.dynamics.com\") as client:\n    records = client.get(\"account\")\n```\n\n---\n\n## 9. Dataverse-Specific Security\n\n### Row-Level Security (RLS)\n\nUser's Dataverse security role determines accessible records:\n\n```python\nfrom azure.identity import InteractiveBrowserCredential\nfrom PowerPlatform.Dataverse.client import DataverseClient\n\n# Each user gets client with their credentials\ndef get_user_client(user_username: str) -\u003e DataverseClient:\n    # User must already be authenticated\n    credential = InteractiveBrowserCredential()\n    \n    client = DataverseClient(\n        base_url=\"https://myorg.crm.dynamics.com\",\n        credential=credential\n    )\n    \n    # User only sees records they have access to\n    return client\n```\n\n### Security Roles\n\nAssign minimal required roles:\n- **System Administrator**: Full access (avoid for apps)\n- **Sales Manager**: Sales tables + reporting\n- **Service Representative**: Service cases + knowledge\n- **Custom**: Create role with specific table permissions\n\n---\n\n## 10. See Also\n\n- [Azure Identity Client Library](https://learn.microsoft.com/en-us/python/api/azure-identity)\n- [Authenticate to Azure Services](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication/overview)\n- [Azure Key Vault for Secrets](https://learn.microsoft.com/en-us/azure/key-vault/general/overview)\n- [Dataverse Security Model](https://learn.microsoft.com/en-us/power-platform/admin/security/security-overview)\n","description":"Based on official Microsoft Azure SDK authentication documentation and Dataverse SDK best practices.","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/dataverse-python-authentication-security.instructions.md"},"manifest":{}},"content_hash":[185,248,109,44,108,54,74,244,171,79,171,130,120,127,103,16,166,224,151,194,2,234,99,33,234,90,113,86,84,17,9,31],"trust_level":"unsigned","yanked":false}
