microsoft / microsoft/Dataverse-skills
auth.py update suggestion for multiple auth_record.json on different tenant/environments
- Dominant language
- No language data
- Stars
- 226
- Forks
- 61
- Avg merge
- 3d 9h
- Merged PRs (30d)
- 7
Description
# `_AUTH_RECORD_PATH` is module-level constant — shared across all projects, causes device code loop when switching tenants
## Problem
`auth.py` computes `_AUTH_RECORD_PATH` once at module import time, before any `.env` is loaded:
```python
_AUTH_RECORD_PATH = Path(os.environ.get("LOCALAPPDATA") or Path.home()) / ".IdentityService" / "dataverse_cli_auth_record.json"
```
This means every project on the machine shares the same `dataverse_cli_auth_record.json` file, regardless of tenant or environment.
**Impact when working across multiple projects/clients in parallel:**
1. Project A authenticates → record written for tenant `aaa` / user `alice@client-a.com`
2. Developer switches to Project B (tenant `bbb`, user `bob@client-b.com`) → `DeviceCodeCredential` loads the stale record from Project A
3. The credential silently attempts a refresh for the wrong tenant/account
4. When the refresh fails and a new device code is issued, the browser picks up the code, routes to the account picker, the user selects the correct account — but Microsoft rejects it because the tenant in the code doesn't match → **infinite redirect loop back to the device code entry page**
The bug is not obvious to diagnose: the error manifests as a browser redirect loop, not a clear authentication failure.
## Proposed fix
Make the path a function, computed lazily after `load_env()` has run, using `TENANT_ID` and the Dataverse hostname as a disambiguating slug:
```python
def _auth_record_path() -> Path:
tenant_id = os.environ.get("TENANT_ID", "")
dataverse_url = os.environ.get("DATAVERSE_URL", "")
base = Path(os.environ.get("LOCALAPPDATA") or Path.home()) / ".IdentityService"
if tenant_id and dataverse_url:
tenant_short = tenant_id.split("-")[0]
try:
from urllib.parse import urlparse
host = urlparse(dataverse_url).hostname or ""
env_slug = host.split(".")[0] # e.g. "contoso-dev" from "contoso-dev.crm4.dynamics.com"
except Exception:
env_slug = ""
slug = f"_{tenant_short}_{env_slug}" if env_slug else f"_{tenant_short}"
return base / f"dataverse_cli_auth_record{slug}.json"
return base / "dataverse_cli_auth_record.json" # safe fallback
```
Then replace every reference to `_AUTH_RECORD_PATH` with a call to `_auth_record_path()` — always after `load_env()` has been called.
**Result:** each environment gets its own record (e.g. `dataverse_cli_auth_record_be91cf8c_contoso-dev.json`), silent refresh works per-project, and parallel multi-client setups no longer interfere with each other.
## Environment
- macOS (same issue applies on Linux; Windows uses `LOCALAPPDATA` which is also per-user but the collision still occurs)
- Multiple Dataverse environments across different tenants on the same machine
- `azure-identity` device code flow with `TokenCachePersistenceOptions`
Contributor guide
Research direction
Start in auth.py by reading load_env(), _AUTH_RECORD_PATH, and every reference to it to confirm when environment values are available. The change is done when authentication records are separated by tenant and Dataverse environment, with the existing fallback preserved, and the device-code flow no longer reuses records across projects.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, python
- Domain
- authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100