Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
24582d3
feat: Add Azure CLI auth source (POC)
Jul 15, 2026
98e325b
Merge branch 'main' into feature/azure-cli-auth-poc
shirasassoon Aug 11, 2026
c0ac959
feat: production-ready Azure CLI auth hardening
Aug 11, 2026
8d48a1a
perf: validate only Fabric scope at Azure CLI login
Aug 11, 2026
aef875a
fix: restore 3-scope validation for Azure CLI login consistency
Aug 11, 2026
a2c5de6
fix: add tenant display to interactive Azure CLI login path
Aug 11, 2026
7c499a2
update
Aug 11, 2026
a0215d2
perf: cache az account show result for 30s to avoid repeated subproce…
Aug 11, 2026
250e8fb
fix: address tenant cache review feedback
Aug 11, 2026
3aed687
Merge branch 'main' into feature/azure-cli-auth-poc
shirasassoon Aug 11, 2026
28bf323
refactor: address code review feedback on Azure CLI auth
Aug 11, 2026
277dd0d
refactor: move Azure CLI error messages to ErrorMessages.Auth
Aug 12, 2026
8ed9892
fix wording
Aug 12, 2026
39b7de1
Merge branch 'feature/azure-cli-auth-poc' of https://github.com/shira…
Aug 12, 2026
cb334eb
refactor: replace denylist sanitization with SDK exception allowlist
Aug 12, 2026
da260d3
refactor: move azure-identity import to module level
Aug 12, 2026
7705598
fix comment
Aug 12, 2026
5be8a2a
chore: remove unused fresh_auth fixture
Aug 12, 2026
22ee7d9
fix: add azure_cli to test args fixture to prevent MagicMock truthy leak
Aug 12, 2026
40213f0
add changelog
Aug 12, 2026
b731895
Merge branch 'feature/azure-cli-auth-poc' of https://github.com/shira…
Aug 12, 2026
3e09380
test: expand Azure CLI auth test coverage
Aug 12, 2026
3c7fd91
test: address review feedback on test quality
Aug 12, 2026
e91c94b
test: add parser and MSAL bridge coverage for Azure CLI auth
Aug 12, 2026
76917c6
test: address review feedback on test quality (round 2)
Aug 12, 2026
af7b21a
test: address review feedback round 3
Aug 12, 2026
cbeba01
test: use monkeypatch.setattr for singleton file paths
Aug 12, 2026
f560488
refactor: extract token refresh buffer to named constant
Aug 12, 2026
02f7618
fix: clear token cache on set_azure_cli to prevent stale cross-tenant…
Aug 12, 2026
8a3ae91
fix: resolve az executable path for Windows compatibility
Aug 12, 2026
a4d4ec4
format fix
Aug 12, 2026
7ec4ee1
docs: add Azure CLI authentication to command reference and examples
Aug 12, 2026
2a6ffe1
docs: add Azure CLI authentication to command reference and examples
Aug 12, 2026
6624a1a
v
Aug 12, 2026
8fae5dc
update docs
Aug 12, 2026
4851ffa
update wording
Aug 12, 2026
ed0b2ed
fix: expand SDK exception allowlist with ServiceRequestError and Serv…
Aug 13, 2026
edfe385
test: verify non-azure-cli identity types do not invoke AzureCliCrede…
Aug 13, 2026
d0c48c2
refactor: move auth isolation tests from bridge to core auth test file
Aug 13, 2026
f4379ad
test: add bidirectional auth method isolation — azure_cli must not in…
Aug 13, 2026
8565bda
chore: align azure-identity minimum to >=1.25.0 (consistent with fabr…
Aug 13, 2026
56878e7
Merge branch 'main' into feature/azure-cli-auth-poc
shirasassoon Aug 17, 2026
72a5916
feat: add principal drift detection to prevent silent identity swaps
Aug 17, 2026
a31a76e
Merge branch 'main' into feature/azure-cli-auth-poc
shirasassoon Aug 19, 2026
502224c
refactor: remove in-memory token cache and globalize AzureCliCredential
Aug 19, 2026
7e11472
Merge branch 'feature/azure-cli-auth-poc' of https://github.com/shira…
Aug 19, 2026
1785775
refactor: single-channel Azure CLI via JWT-based drift detection
Aug 19, 2026
8b82831
feat: add issuer (iss) to drift detection key for sovereign cloud safety
Aug 19, 2026
2d38b97
security: fail-closed on missing JWT identity claims
Aug 20, 2026
f819ded
refactor: strict Azure CLI context inheritance, no tenant override
Aug 20, 2026
165aa7d
feat: validate --tenant flag against Azure CLI context
Aug 20, 2026
0dcba92
fix: environment drift check compares issuer host only, not full URL
Aug 20, 2026
acb923b
fix: suppress noisy Azure SDK logging in fab auth status
Aug 20, 2026
3ff1e13
fix: correct mock assertions for keyword arg in set_azure_cli tests
Aug 20, 2026
769b0cf
fix error messages
Aug 20, 2026
3e32629
Merge branch 'feature/azure-cli-auth-poc' of https://github.com/shira…
Aug 20, 2026
bda02b2
fux
Aug 20, 2026
72aa017
fix
Aug 20, 2026
8d7f062
fix: update test assertions to match simplified error messages
Aug 20, 2026
316cb63
refactor: use signature-validated _decode_jwt_token for Azure CLI auth
Aug 23, 2026
0140a7e
fix: add JWKS bypass monkeypatch to MSAL bridge Azure CLI tests
Aug 23, 2026
0da5b2f
security: suppress azure.identity/azure.core loggers globally
Aug 23, 2026
11a19a2
docs: clarify --tenant flag behavior for Azure CLI auth
Aug 23, 2026
abf66a8
fix: roll back partial auth state when Azure CLI probe fails on mode …
Aug 23, 2026
b6b793c
security: use isinstance for SDK exception allowlist instead of name-…
Aug 23, 2026
b322242
fix: always logout on failed probe for consistency with MSAL flow
Aug 23, 2026
0448b28
security: apply full SDK exception allowlist to probe path
Aug 23, 2026
7a80a21
fix: distinguish drift errors from session expiry in auth status
Aug 23, 2026
d818994
feedback
Aug 24, 2026
cb47d13
update
Aug 24, 2026
d4ba520
remove auth_source field for consistency
Aug 24, 2026
7ad145e
remove redundancy
Aug 24, 2026
b2b0f5d
feat(auth): remove drift detection and simplify Azure CLI auth flow
Aug 24, 2026
1549b24
fix(auth): remove Azure CLI success message for MSAL consistency
Aug 24, 2026
8827af1
update docs
Aug 24, 2026
1c0faeb
fix
Aug 24, 2026
76a7da7
revert
Aug 24, 2026
e0bffc7
fix(auth): always update tenant from Azure CLI token on each acquisition
Aug 25, 2026
f9405e4
fix(auth): bypass set_tenant for Azure CLI to prevent partial logout
Aug 25, 2026
a4c3339
clean up
Aug 25, 2026
fb7bcd1
add azure sdk logging suppression
Aug 25, 2026
794d830
fix bug with tenant and status ui
Aug 25, 2026
294e050
Merge branch 'main' into feature/azure-cli-auth-poc
shirasassoon Aug 25, 2026
0369efd
add timeout, fix failing tests, clean up import, remove decode jwt lo…
Aug 25, 2026
5efe6d4
Merge branch 'feature/azure-cli-auth-poc' of https://github.com/shira…
Aug 25, 2026
73fec4a
add space back
Aug 25, 2026
5f0ed58
add arg validation
Aug 25, 2026
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
6 changes: 6 additions & 0 deletions .changes/unreleased/added-20260812-122446.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: added
body: Add support for Azure CLI authentication source
time: 2026-08-12T12:24:46.8603823+03:00
custom:
Author: shirasassoon
AuthorLink: https://github.com/shirasassoon
35 changes: 28 additions & 7 deletions docs/commands/auth/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ Not resource-specific; applies to CLI authentication context.

## Available Commands

| Command | Description | Usage |
|----------------|---------------------------|-----------------------------------------------------------------------|
| `auth login` | Log in to Fabric CLI | `auth login [parameters]` |
| `auth logout` | Log out of current session| `auth logout` |
| `auth status` | Show authentication status| `auth status` |
| Command | Description | Usage |
| --- | --- | --- |
| `auth login` | Log in to Fabric CLI | `auth login [parameters]` |
| `auth logout` | Log out of current session | `auth logout` |
| `auth status` | Show authentication status | `auth status` |

---

Expand All @@ -22,8 +22,28 @@ Authenticate with Fabric CLI.

**Usage:**

#### Interactive login
```
fab auth login [-u <client_id>] [-p <client_secret>] [--federated-token <token>] [--certificate </path/to/certificate.[pem|p12|pfx]>] [--tenant <tenant_id>]
fab auth login
```

#### Azure CLI
```
fab auth login --azure-cli
```

#### Service principal
```
# Service principal with secret
fab auth login -u <client_id> -p <client_secret> --tenant <tenant_id>

# Service principal with certificate
fab auth login -u <client_id> --certificate <path> --tenant <tenant_id>
```

#### Workload identity
```
fab auth login -u <client_id> --federated-token <token> --tenant <tenant_id>
```

**Parameters:**
Expand All @@ -32,6 +52,7 @@ fab auth login [-u <client_id>] [-p <client_secret>] [--federated-token <token>]
- `-p, --password`: Client secret for service principal. Optional.
- `--federated-token`: Federated token for workload identity. Optional.
- `--certificate`: Path to certificate file. Optional.
- `--azure-cli`: Use an existing Azure CLI login session as the token provider. Requires Azure CLI to be installed and logged in (`az login`). Optional.
- `--tenant`: Tenant ID. Optional.

---
Expand Down Expand Up @@ -60,4 +81,4 @@ fab auth status

---

For more examples and detailed scenarios, see [Authentication Examples](../../examples/auth_examples.md).
For more examples and detailed scenarios, see [Authentication Examples](../../examples/auth_examples.md).
19 changes: 19 additions & 0 deletions docs/examples/auth_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ fab auth login
```


### Azure CLI Authentication

Reuse an _existing_ Azure CLI session instead of requiring a separate Fabric CLI login. Useful when tools or scripts already have `az login` done (e.g., in development environments or CI/CD pipelines with Azure CLI pre-authenticated).

!!! info "Requires Azure CLI to be installed and logged in (`az login`)"

#### Log in using Azure CLI in interactive mode

```
fab auth login
? How would you like to authenticate Fabric CLI? Azure CLI (existing 'az login' session)
```

#### Log in using Azure CLI directly from command line

```
fab auth login --azure-cli
```

### Service Principal Authentication

!!! info "Requires 'Allow service principals to use Fabric APIs' tenant switch to be enabled in the admin portal"
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies = [
"msal>=1.34,<2",
"msal_extensions",
"azure-core>=1.29.0",
"azure-identity>=1.25.0",
"questionary",
"prompt_toolkit>=3.0.41",
"cachetools>=5.5.0",
Expand Down
116 changes: 89 additions & 27 deletions src/fabric_cli/commands/auth/fab_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@
def init(args: Namespace) -> Any:
auth_options = [
"Interactive with a web browser",
"Azure CLI (existing 'az login' session)",
"Service principal authentication with secret",
"Service principal authentication with certificate",
"Service principal authentication with federated credential",
"Managed identity authentication",
]

_validate_auth_args(args)

utils_mem_store.clear_caches()

# Clean up stale context files when logging in
Context().cleanup_context_files(cleanup_all_stale=True, cleanup_current=False)

if args.identity:
if getattr(args, "azure_cli", False):
FabAuth().set_access_mode("azure_cli")
Comment thread
shirasassoon marked this conversation as resolved.
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment on lines +33 to +36

elif args.identity:
FabAuth().set_access_mode("managed_identity")
FabAuth().set_managed_identity(args.username)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()

elif any([args.username, args.password]):
elif any([args.username, args.password, args.certificate, args.federated_token]):
if not (
all([args.username, args.tenant])
and any([args.password, args.certificate, args.federated_token])
Expand All @@ -54,9 +60,7 @@ def init(args: Namespace) -> Any:
FabAuth().set_spn(args.username, password=args.password)
elif args.federated_token:
FabAuth().set_spn(args.username, client_assertion=args.federated_token)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()
else:
selected_auth = fab_ui.prompt_select_item(
Expand All @@ -69,9 +73,11 @@ def init(args: Namespace) -> Any:
try:
if selected_auth == "Interactive with a web browser":
FabAuth().set_access_mode("user", args.tenant)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()
elif selected_auth.startswith("Azure CLI"):
FabAuth().set_access_mode("azure_cli")
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()
elif selected_auth.startswith("Service principal authentication"):
fab_logger.log_warning(
Expand Down Expand Up @@ -174,9 +180,7 @@ def init(args: Namespace) -> Any:
FabAuth().set_spn(client_id, password=client_secret)
elif federated_token:
FabAuth().set_spn(client_id, client_assertion=federated_token)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()
elif selected_auth == "Managed identity authentication":
fab_logger.log_warning(
Expand All @@ -191,9 +195,7 @@ def init(args: Namespace) -> Any:

FabAuth().set_access_mode("managed_identity")
FabAuth().set_managed_identity(client_id)
FabAuth().get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
FabAuth().get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
_acquire_default_access_tokens(FabAuth())
Context().context = FabAuth().get_tenant()

except KeyboardInterrupt:
Expand All @@ -217,6 +219,7 @@ def logout(args: Namespace) -> None:

def status(args: Namespace) -> None:
auth = FabAuth()
identity_type = auth.get_identity_type()
tenant_id = auth.get_tenant_id()

def __get_token_info(scope):
Expand Down Expand Up @@ -273,21 +276,80 @@ def __mask_token(scope):
)
fab_ui.print_grey(login_status)

auth_data = {
"logged_in": is_logged_in,
"account": upn,
"principal_id": oid,
"tenant_id": tid,
"app_id": appid,
"token_fabric_powerbi": fabric_secret,
"token_storage": storage_secret,
"token_azure": azure_secret,
}
auth_data = {}
if identity_type == "azure_cli":
auth_data.update(
{
"authentication_mode": "Azure CLI",
"azure_cli_session": "Available" if is_logged_in else "Unavailable",
}
)
auth_data.update(
{
"logged_in": is_logged_in,
"account": upn,
"principal_id": oid,
"tenant_id": tid,
"app_id": appid,
"token_fabric_powerbi": fabric_secret,
"token_storage": storage_secret,
"token_azure": azure_secret,
}
)
fab_ui.print_output_format(args, data=auth_data, show_key_value_list=True)


# Utils
def _validate_auth_args(args: Namespace) -> None:
auth_args = {
"azure_cli": "--azure-cli",
"identity": "--identity",
"username": "--username",
"password": "--password",
"tenant": "--tenant",
"certificate": "--certificate",
"federated_token": "--federated-token",
}
specified_args = [
attribute for attribute in auth_args if getattr(args, attribute, None)
]

incompatible: list[str] = []
if "azure_cli" in specified_args:
incompatible = specified_args if len(specified_args) > 1 else []
elif "identity" in specified_args:
incompatible = [
attribute
for attribute in specified_args
if attribute not in {"identity", "username"}
]
if incompatible:
incompatible.insert(0, "identity")
elif "federated_token" in specified_args:
incompatible = [
attribute
for attribute in specified_args
if attribute in {"password", "certificate"}
]
if incompatible:
incompatible.append("federated_token")

if incompatible:
raise FabricCLIError(
ErrorMessages.Auth.incompatible_authentication_arguments(
[auth_args[attribute] for attribute in incompatible]
),
fab_constant.ERROR_INVALID_INPUT,
)


def _get_token_info_from_bearer_token(bearer_token: str) -> Optional[dict[str, str]]:
return FabAuth()._get_claims_from_token(
bearer_token, ["upn", "oid", "tid", "appid"]
)


def _acquire_default_access_tokens(auth: FabAuth) -> None:
auth.get_access_token(scope=fab_constant.SCOPE_FABRIC_DEFAULT)
auth.get_access_token(scope=fab_constant.SCOPE_ONELAKE_DEFAULT)
auth.get_access_token(scope=fab_constant.SCOPE_AZURE_DEFAULT)
Loading