Skip to content

feat: add --use-device-code flag to auth login#257

Closed
iemejia wants to merge 2 commits into
microsoft:mainfrom
iemejia:login-device-code
Closed

feat: add --use-device-code flag to auth login#257
iemejia wants to merge 2 commits into
microsoft:mainfrom
iemejia:login-device-code

Conversation

@iemejia

@iemejia iemejia commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Add device code authentication flow for fab auth login, similar to az login --use-device-code in Azure CLI. This allows users to authenticate from environments where browser access is not available (SSH sessions, containers, remote servers, CI runners).

Usage

fab auth login --use-device-code
fab auth login --use-device-code --tenant <tenant_id>

The option is also available in the interactive authentication menu as "Device code".

Prerequisite: App registration change

The Fabric CLI app registration (5814bfb4-2705-4994-b8d6-39aabeb5eaeb) is configured as a confidential client. Device code flow requires "Allow public client flows" to be enabled:

  • Azure Portal: App registrations > Authentication > Advanced settings > Allow public client flows > Yes
  • Manifest: "allowPublicClient": true

Implementation

  • --use-device-code argument added to the auth login parser
  • _acquire_token_by_device_code() method using MSAL's initiate_device_flow / acquire_token_by_device_flow
  • Public FabAuth.device_code_flow() context manager to enable device code mode from the commands layer
  • Silent refresh with force_refresh=True for subsequent scopes (user enters code only once)
  • Fixed acquire_token_silent error handling: MSAL can return {"error": ...} (not just None)
    Tests
  • 8 command-level tests (CLI flag, tenant arg, interactive selection, precedence, flag cleanup)
  • 11 core-level tests (success, silent refresh, fallbacks, error cases, KeyboardInterrupt)

Copilot AI review requested due to automatic review settings June 30, 2026 21:06
@iemejia iemejia requested a review from a team as a code owner June 30, 2026 21:06
Add device code authentication flow for environments where browser
access is not available. Users can now authenticate by entering a code
at https://microsoft.com/devicelogin from any device with a browser.

Usage:
  fab auth login --use-device-code
  fab auth login --use-device-code --tenant <tenant_id>

The option is also available in the interactive authentication menu
as "Device code".

Implementation:
- Add --use-device-code argument to the auth login parser
- Add _acquire_token_by_device_code() using MSAL's initiate_device_flow
  and acquire_token_by_device_flow APIs
- Add device_code_flow_failed() error message
- Wrap device code flow in try/finally to ensure flag cleanup on failure
- Use silent refresh for subsequent scopes after initial device code auth

Assisted-by: GitHub Copilot:claude-opus-4.6
@iemejia iemejia force-pushed the login-device-code branch from 7aa9a30 to fb068dc Compare June 30, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a device-code authentication option to fab auth login to support non-browser environments (SSH/containers/CI) and updates core auth logic to better handle MSAL silent acquisition errors.

Changes:

  • Adds --use-device-code flag and “Device code” interactive option to the auth login command.
  • Implements device-code token acquisition in FabAuth and routes user auth through it when enabled.
  • Adds/updates core + command-level tests and a changelog entry.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/fabric_cli/parsers/fab_auth_parser.py Adds CLI flag and updates examples for device-code login.
src/fabric_cli/commands/auth/fab_auth.py Wires new flag + interactive selection into the login command flow.
src/fabric_cli/core/fab_auth.py Adds device-code flow implementation + dispatch logic in token acquisition.
src/fabric_cli/errors/auth.py Adds a dedicated error message for device-code initiation failures.
tests/test_core/test_fab_auth.py Adds extensive unit tests for the device-code flow and error cases.
tests/test_commands/test_auth.py Adds command-level tests for flag behavior, precedence, and cleanup.
.changes/unreleased/added-20260630-223439.yaml Changelog entry documenting the new flag.

Comment on lines +31 to +37
if args.use_device_code:
FabAuth().set_access_mode("user", args.tenant)
with FabAuth().device_code_flow():
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)
Context().context = FabAuth().get_tenant()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — using a single auth = FabAuth() instance now. Note: FabAuth is a singleton, so this was functionally correct before, but a local variable makes the intent explicit.

Comment on lines +85 to +91
elif selected_auth == "Device code":
FabAuth().set_access_mode("user", args.tenant)
with FabAuth().device_code_flow():
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)
Context().context = FabAuth().get_tenant()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment on lines +568 to 574
prompt="select_account",
parent_window_handle=msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE,
)
if token is not None and "id_token_claims" in token:
self.set_tenant(token.get("id_token_claims")["tid"])

if token and token.get("error"):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — using .get("tid") with a truthy guard before calling set_tenant().

Comment thread src/fabric_cli/core/fab_auth.py Outdated
Comment on lines +293 to +295
def _acquire_token_by_device_code(self, scope: list[str]) -> Optional[dict]:
"""
Acquire a token using the device code flow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — renamed to scopes to align with MSAL's parameter naming.

Comment on lines +306 to +309
Returns:
Full MSAL result dictionary containing access_token, expires_on, etc.,
or None if the device flow returns no result. Raises:
FabricCLIError: When device code flow initiation or token acquisition fails

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added a blank line before Raises: section.

Comment thread src/fabric_cli/core/fab_auth.py Outdated
Comment on lines +314 to +318
accounts = self._get_app().get_accounts()
if accounts:
token = self._get_app().acquire_token_silent(
scopes=scope, account=accounts[0], force_refresh=True
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — stored as app = self._get_app() and reused throughout the method.

Comment thread src/fabric_cli/core/fab_auth.py Outdated
if token and token.get("access_token"):
return token

flow = self._get_app().initiate_device_flow(scopes=scope)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment thread src/fabric_cli/core/fab_auth.py Outdated
utils_ui.print_grey(flow["message"])

# Wait for the user to authenticate
token = self._get_app().acquire_token_by_device_flow(flow)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment thread src/fabric_cli/core/fab_auth.py Outdated
Comment on lines +68 to +70
Usage:
with FabAuth().device_code_flow():
FabAuth().get_access_token(scope=...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — docstring now shows auth = FabAuth(); with auth.device_code_flow(): ...

Copilot AI review requested due to automatic review settings June 30, 2026 21:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines +306 to +310
Returns:
Full MSAL result dictionary containing access_token, expires_on, etc.,
or None if the device flow returns no result. Raises:
FabricCLIError: When device code flow initiation or token acquisition fails
"""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix as above — already addressed

- Use single FabAuth instance in device code login paths for clarity
- Guard id_token_claims['tid'] with .get() to avoid KeyError
- Rename scope -> scopes in _acquire_token_by_device_code for MSAL alignment
- Fix docstring formatting (Raises: on its own line)
- Store self._get_app() in local variable to reduce repeated calls
- Fix usage example in device_code_flow() docstring to show single instance

Assisted-by: GitHub Copilot:claude-opus-4.6
@ayeshurun

Copy link
Copy Markdown
Collaborator

Hi @iemejia,

Thank you for the contribution. Please note that authentication is an area with restricted contributions, as outlined in our contributing guide: https://github.com/microsoft/fabric-cli/blob/main/CONTRIBUTING.md#areas-with-restricted-contributions

Additionally, external contributions should follow CONTRIBUTING.md and address issues labelled help wanted or good first issue.

For these reasons, we're closing this PR. Thank you again for your interest, and we hope to see more from you on eligible issues.

@iemejia

iemejia commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @ayeshurun, understood -- I should have followed the process and linked this to an issue first. My apologies for the shortcut.

I've commented on the existing issue #215 with expanded use cases (headless servers, SSH sessions, containers, CI runners) and offered to resubmit once the team confirms the approach and labels it accordingly.

Happy to rework the PR whenever the team is ready to accept contributions on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants