Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/skills/find-package-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ you already know the package well.

| Package | Path |
| ------------------------- | --------------------------------------------------------------------------- |
| `azure-search-documents` | `sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md` |
| `azure-search-documents` | `sdk/search/azure-search-documents/.github/skills/azure-search-documents/SKILL.md` |
| `azure-identity` | `sdk/identity/azure-identity/.github/skills/azure-identity/SKILL.md` |
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
name: azure-identity
description: 'Domain knowledge for azure-identity. Covers architecture, credential chain, MSAL integration, sync/async split, managed identity, token APIs, and common pitfalls. WHEN: modify azure-identity; fix azure-identity bug; add azure-identity credential; add azure-identity feature; change azure-identity DefaultAzureCredential; update azure-identity managed identity.'
---

# azure-identity — Package Skill

`azure-identity` is **entirely hand-authored**: no TypeSpec, no `_generated/`, no `_patch.py`, and no regeneration step. Every changed file is production source, so first verify public API exports, sync/async parity, credential-chain behavior, and tests.

```
azure/identity/
├── __init__.py # public sync exports; __all__ is the contract
├── _credentials/ # sync credentials, DAC, managed identity dispatcher
├── _internal/ # shared auth plumbing; not public API
└── aio/ # async mirror, with fewer public credentials
```

**Extension package:** `azure-identity-broker` provides `InteractiveBrowserBrokerCredential` (sync only) for broker auth on Windows, macOS, Linux, and WSL, with non-Windows/WSL fallback behavior. `VisualStudioCodeCredential` requires this package and only works on Windows/WSL.

## Common Pitfalls

1. **Public symbols require `__all__`.** Add new sync public symbols to `azure/identity/__init__.py`; add async-applicable symbols to `azure/identity/aio/__init__.py`.
2. **Three credentials are sync-only.** Do not add async `DeviceCodeCredential`, `InteractiveBrowserCredential`, or `UsernamePasswordCredential`.
3. **MSAL is sync-only.** Async credentials may reuse MSAL `TokenCache`, but never instantiate `msal.PublicClientApplication`, `ConfidentialClientApplication`, or `ManagedIdentityClient`.
4. **DAC chain order is contract-sensitive.** Sync DAC has Environment, WorkloadIdentity, ManagedIdentity, SharedTokenCache, VS Code, CLI, PowerShell, Developer CLI, InteractiveBrowser (excluded by default), Broker. Async DAC stops after Developer CLI.
5. **`within_dac` is intentional.** DAC sets this `ContextVar` while iterating so child credentials suppress noisy errors and exit fast on `CredentialUnavailableError`.
6. **Managed identity dispatch is environment-driven.** Cloud Shell uses the in-house `ManagedIdentityClient`; most other managed-identity hosts use `MsalManagedIdentityClient`; Workload Identity dispatches to `WorkloadIdentityCredential`.
7. **Never hardcode authorities.** Use `AzureAuthorityHosts`, `get_default_authority()`, and `normalize_authority()` so sovereign clouds work.

## Token API Rule

Every credential supports `get_token()` and `get_token_info()` through `GetTokenMixin`. `get_token()` is legacy but supported; new token-response features such as PoP, `refresh_on`, or non-Bearer token types belong on `get_token_info()` / `TokenRequestOptions`.

## Change Checklist

1. Mirror sync changes under `aio/` unless the credential is one of the three sync-only credentials.
2. Keep shared helpers in sync `_internal/` and import them from async; do not duplicate logic.
3. If DAC changes, update both sync and async defaults, exclude kwargs, `AZURE_TOKEN_CREDENTIALS` behavior, and tests.
4. If token acquisition changes, preserve CAE/non-CAE cache separation and multi-tenant `additionally_allowed_tenants` checks.
5. Update `CHANGELOG.md`, README snippets, and troubleshooting links when public behavior changes.

After code changes, run the package validation tool:

```
azsdk_package_run_check with packagePath="sdk/identity/azure-identity" and checkType="All"
```

For targeted local checks from `sdk/identity/azure-identity`, use `azpysdk pytest .`, `azpysdk pylint .`, `azpysdk mypy .`, and `azpysdk sphinx .`.

## References

- Credential taxonomy, MSAL tiers, managed identity, DAC details: `references/architecture.md`
- Test commands and patterns: `references/testing.md`
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Architecture Reference for azure-identity

## Credential Taxonomy

### By OAuth Application Type

This is the OAuth/MSAL distinction that drives most of the credential design.

**Public client application** — cannot keep a secret; authenticates a _user_. This is the OAuth category; implementation varies by credential, so check the MSAL tiers below before changing internals:
- `InteractiveBrowserCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential`
- `AuthorizationCodeCredential`, `SharedTokenCacheCredential`
- `VisualStudioCodeCredential` (delegates to broker)
- `InteractiveBrowserBrokerCredential` (broker package)

**Confidential client application** — runs on a server, holds a secret/certificate/assertion. This is the OAuth category; not every credential here instantiates `msal.ConfidentialClientApplication` directly:
- `ClientSecretCredential`, `CertificateCredential`, `ClientAssertionCredential`
- `OnBehalfOfCredential`
- `WorkloadIdentityCredential` (built on `ClientAssertionCredential`)
- `AzurePipelinesCredential` (composes `ClientAssertionCredential`)
- `EnvironmentCredential` (resolves to one of the above)

**Neither (managed identity)** — authenticates the compute resource via local IMDS/metadata endpoint:
- `ManagedIdentityCredential` and backends: `ImdsCredential`, `AppServiceCredential`, `AzureMLCredential`, `AzureArcCredential`, `ServiceFabricCredential`, `CloudShellCredential`

**Subprocess-based** — shells out to a developer tool that has already authenticated (no MSAL):
- `AzureCliCredential`, `AzureDeveloperCliCredential`, `AzurePowerShellCredential`

### By Production vs Developer (DAC Classification)

Used by `DefaultAzureCredential` and the `AZURE_TOKEN_CREDENTIALS` env var:

| Category | Credentials |
|----------|------------|
| **Production** | `EnvironmentCredential`, `WorkloadIdentityCredential`, `ManagedIdentityCredential` |
| **Developer** | `SharedTokenCacheCredential`, `VisualStudioCodeCredential`, `AzureCliCredential`, `AzurePowerShellCredential`, `AzureDeveloperCliCredential`, `BrokerCredential` |
| **Other** (explicit use only) | `ClientSecretCredential`, `CertificateCredential`, `ClientAssertionCredential`, `OnBehalfOfCredential`, `AuthorizationCodeCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential`, `AzurePipelinesCredential`, `InteractiveBrowserCredential` (DAC-excluded by default) |

## MSAL Integration — Three Tiers

Understanding the three tiers of MSAL usage is critical for making changes:

### Tier 1: Full MSAL client app (sync only)

These instantiate `msal.PublicClientApplication`, `msal.ConfidentialClientApplication`, or `msal.ManagedIdentityClient`:
- `ClientSecretCredential`, `CertificateCredential` → `ConfidentialClientApplication`
- `OnBehalfOfCredential` → `ConfidentialClientApplication.acquire_token_on_behalf_of`
- `InteractiveBrowserCredential`, `DeviceCodeCredential`, `UsernamePasswordCredential` → `PublicClientApplication`
- `ManagedIdentityCredential` (most backends) → `MsalManagedIdentityClient` → `msal.ManagedIdentityClient`
- **Exception:** `CloudShellCredential` does NOT use `msal.ManagedIdentityClient` — uses in-house `ManagedIdentityClient`

### Tier 2: MSAL TokenCache only

The in-house `AadClient` performs raw OAuth HTTP requests but stores results in `msal.TokenCache` for cache compatibility:
- `AuthorizationCodeCredential`, `ClientAssertionCredential`, `WorkloadIdentityCredential`, `AzurePipelinesCredential`
- `SharedTokenCacheCredential` — reads MSAL's persisted cache, redeems refresh tokens via `AadClient`

### Tier 3: No MSAL at all

Subprocess-based credentials that parse stdout JSON:
- `AzureCliCredential` (`az account get-access-token`)
- `AzureDeveloperCliCredential` (`azd auth token`)
- `AzurePowerShellCredential` (`Get-AzAccessToken` via `pwsh`)

### Sync vs Async — The Big Rule

**MSAL is a sync-only library.** No async credential ever instantiates a MSAL client application. Async credentials inherit from sync base classes that import MSAL, but token acquisition on the async side is implemented in-house:
- Async `AadClient` performs raw OAuth HTTP requests via azure-core's async pipeline
- Async managed-identity sources hit IMDS/app-host endpoints directly
- MSAL's `TokenCache` data structure IS reused for cache compatibility

So Tier 1 sync credentials do not stay Tier 1 in async: their async counterparts either use `AadClient` / `TokenCache` compatibility or direct async managed-identity HTTP.

## Managed Identity Backends

`ManagedIdentityCredential` sniffs the environment and picks ONE backend:

| Environment | Detection | Uses MSAL MI Client? |
|------------|-----------|---------------------|
| App Service | `IDENTITY_ENDPOINT` + `IDENTITY_HEADER` | Yes |
| Service Fabric | `IDENTITY_ENDPOINT` + `IDENTITY_SERVER_THUMBPRINT` | Yes |
| Azure Arc | `IDENTITY_ENDPOINT` + `IMDS_ENDPOINT` | Yes |
| Azure ML | `MSI_ENDPOINT` + `MSI_SECRET` | Yes |
| Cloud Shell | `MSI_ENDPOINT` (no secret) | **No** — uses in-house client |
| Workload Identity | `AZURE_FEDERATED_TOKEN_FILE` + `AZURE_TENANT_ID` + `AZURE_CLIENT_ID` | No — dispatches to `WorkloadIdentityCredential` / `AadClient` |
| IMDS (fallback) | None of the above | Yes |

## DefaultAzureCredential Chain

### Sync chain (10 entries)

1. `EnvironmentCredential`
2. `WorkloadIdentityCredential`
3. `ManagedIdentityCredential`
4. `SharedTokenCacheCredential`
5. `VisualStudioCodeCredential`
6. `AzureCliCredential`
7. `AzurePowerShellCredential`
8. `AzureDeveloperCliCredential`
9. `InteractiveBrowserCredential` (**excluded by default** — `exclude_interactive_browser_credential=True`)
10. `BrokerCredential` (no-op unless `azure-identity-broker` installed + Windows/WSL)

### Async chain (8 entries)

Same as sync entries 1–8. **No** `InteractiveBrowserCredential` or `BrokerCredential`.

### `FailedDACCredential` pattern

If a credential fails to **initialize** (not at token time), it's replaced with `FailedDACCredential` (sync) or `AsyncFailedDACCredential` (async). This lets the chain continue and reports the init error only if ALL credentials fail.

### `AZURE_TOKEN_CREDENTIALS` env var

Narrows DAC before `exclude_*` flags:
- `prod` → keeps only Environment, WorkloadIdentity, ManagedIdentity
- `dev` → keeps developer credentials + broker
- Specific credential name → selects one (but `SharedTokenCacheCredential` and `BrokerCredential` have no `env_name` and can't be selected this way)
- `require_envvar=True` + unset var → `ValueError`

### `within_dac` ContextVar

Set while DAC iterates its chain. Child credentials check this to:
- Suppress noisy error logging (use DEBUG instead of WARNING)
- Exit fast on `CredentialUnavailableError`

## Authentication Flows

| Flow | Credentials |
|------|------------|
| Client credentials — secret | `ClientSecretCredential`, `EnvironmentCredential` (with `AZURE_CLIENT_SECRET`) |
| Client credentials — certificate | `CertificateCredential`, `EnvironmentCredential` (with `AZURE_CLIENT_CERTIFICATE_PATH`) |
| Client credentials — federated assertion | `ClientAssertionCredential` |
| Workload identity federation | `WorkloadIdentityCredential` |
| Azure Pipelines OIDC | `AzurePipelinesCredential` |
| On-Behalf-Of | `OnBehalfOfCredential` |
| IMDS / app-host metadata | `ManagedIdentityCredential` (auto-detects source) |
| Authorization code + PKCE | `InteractiveBrowserCredential`, `InteractiveBrowserBrokerCredential` |
| Authorization code redemption | `AuthorizationCodeCredential` |
| Device code | `DeviceCodeCredential` |
| ROPC (discouraged) | `UsernamePasswordCredential` |
| Silent / refresh-token from cache | `SharedTokenCacheCredential`, silent step in interactive credentials |
| Native broker (WAM) | `InteractiveBrowserBrokerCredential`, `VisualStudioCodeCredential` |
| Subprocess delegation | `AzureCliCredential`, `AzurePowerShellCredential`, `AzureDeveloperCliCredential` |

## Cross-Cutting Behaviors

- **CAE**: Credentials that proxy MSAL pass `enable_cae` through. `AadClient` keeps a separate `cae_cache`. CAE and non-CAE tokens must not collide.
- **Multi-tenant**: Per-call `tenant_id` overrides constructor tenant, subject to `additionally_allowed_tenants` allow-list.
- **Token caching**: In-memory by default. Persistent via `TokenCachePersistenceOptions` → `msal-extensions` (DPAPI/Keychain/libsecret, plaintext fallback only if explicitly allowed).
- **Authorities/clouds**: Controlled by `authority=` or `AZURE_AUTHORITY_HOST`. `AzureAuthorityHosts` enumerates Public, US Gov, China.
- **Logging**: Every credential logs through `azure.identity` logger. `TROUBLESHOOTING.md` is the canonical troubleshooting reference.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Testing Guide for azure-identity

## Running Tests

```bash
cd sdk/identity/azure-identity

# Run unit tests (playback mode, no credentials needed)
azpysdk pytest .

# Run a specific test file
azpysdk pytest tests/test_default.py

# Run async tests
azpysdk pytest tests/test_default_async.py

# Run with keyword filter
azpysdk pytest tests/ -k "test_default_credential"
```

## Test Structure

Tests generally mirror the credential layout, but sync-only credentials do not have async test pairs:

```
tests/
├── test_default.py / test_default_async.py
├── test_managed_identity.py / test_managed_identity_async.py
├── test_certificate_credential.py / test_certificate_credential_async.py
├── test_cli_credential.py / test_cli_credential_async.py
├── test_browser_credential.py # sync-only credential
├── test_chained_credential.py / test_chained_credential_async.py
├── managed-identity-live/ # Live MI tests (require Azure environment)
├── helpers.py # Shared test utilities
└── conftest.py # Fixtures, sanitizers, env-gated marks
```

## Key Testing Patterns

- **`time.sleep` and `asyncio.sleep` are patched to no-op** in non-live mode (`conftest.py`). Don't rely on timing in unit tests.
- **Custom markers**: `@pytest.mark.manual` (requires human interaction), `@pytest.mark.prints` (outputs to terminal).
- **Live tests are env-gated**: Service principal creds, certificate paths, and username/password must be in env vars. Tests skip if vars are missing.
- **Managed identity live tests** are in a separate `managed-identity-live/` directory with their own `conftest.py`. They require Key Vault access and specific environment configuration.
- **MSAL app behavior is often mocked** with `unittest.mock.patch`, while HTTP transports are also used where request/response behavior matters (for example, token endpoint flows and managed identity clients).
- **Async tests** use `pytest-asyncio`. The event loop policy is overridden on Windows to `WindowsSelectorEventLoopPolicy` in `conftest.py`.

## Validation

```bash
# Run targeted local checks
azpysdk pytest .
azpysdk pylint .
azpysdk mypy .
azpysdk sphinx .
```

For full package validation through the Azure SDK release tooling, run:

```
azsdk_package_run_check with packagePath="sdk/identity/azure-identity" and checkType="All"
```
Loading