microsoft / microsoft/Dataverse-skills

auth.py update suggestion for multiple auth_record.json on different tenant/environments

Open
#86 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.