Skip to content

Device code auth - #7093

Open
maksim-zuev-db wants to merge 24 commits into
mainfrom
device-code
Open

maksim-zuev-db wants to merge 24 commits into
mainfrom
device-code

Conversation

@maksim-zuev-db

@maksim-zuev-db maksim-zuev-db commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Changes

Implementation of device code auth flow. The backend implementation is still not finalized in production, so this change introduces --use-device-code flag as experimental, hidden under DATABRICKS_CLI_DEVICE_CODE env variable.

Why

The Device Code flow enables users to authenticate with the Databricks CLI on machines that don't have browser access (e.g. headless servers, SSH sessions, web terminals). Instead of the standard browser-redirect OAuth flow, the user authenticates on a separate device (e.g. their phone or laptop) by entering a short code.

Tests

  • Unit tests cover the protocol logic and the auth login helpers, including
    edge cases like expiry, retries and cancellation.
  • Acceptance tests run the CLI end to end against libs/testserver, which now
    models Databricks' device code flow. Confirmation codes are piped through
    stdin. The tests cover the successful login and each error scenario.
  • Manual tests: only dev environments support device code now.

@maksim-zuev-db
maksim-zuev-db requested review from a team as code owners October 16, 2026 15:50
@maksim-zuev-db
maksim-zuev-db marked this pull request as draft October 16, 2026 15:51
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 419aeb6

Run: 39968670549

Env 🔄​flaky ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 281 20 4:07
✅​ aws-windows-latest-4core-16gb 283 18 5:07
🔄​ azure linux-2core-8gb 2 278 20 4:27
✅​ azure-windows-latest-4core-16gb 282 18 4:00
✅​ gcp linux-2core-8gb 281 20 4:11
✅​ gcp-windows-latest-4core-16gb 283 18 3:39
Test Name azure linux-2core-8gb
🔄​ TestFsCpDir/uc-volumes_to_local 🔄​f
🔄​ TestFsCpDir/uc-volumes_to_uc-volumes 🔄​f
Top 3 slowest tests (at least 2 minutes):
duration env testname
5:00 aws-windows-latest-4core-16gb TestAccept
3:53 azure-windows-latest-4core-16gb TestAccept
3:33 gcp-windows-latest-4core-16gb TestAccept

Comment thread cmd/auth/login.go Outdated
Comment thread cmd/auth/login_device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go Outdated
Comment thread acceptance/cmd/auth/login/use-device-code/output.txt Outdated
Comment thread acceptance/cmd/auth/login/use-device-code/output.txt Outdated
maksim-zuev-db and others added 7 commits October 22, 2026 11:04
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
The server answers a rate-limited device code token request with
invalid_request too, but with HTTP 429. Entering the confirmation code
again cannot fix that, so end the login for any error other than
HTTP 400 with invalid_request.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
The device code host check uses it to reject account and unified hosts, not to decide whether to prompt. Keep needsAccountIDPrompt as a local variable where setHostAndAccountId decides to prompt.

Co-authored-by: Isaac <no-reply@databricks.com>
CheckDeviceCodeSupported fetched the OIDC endpoints with its own client and repeated the check DeviceCodeChallenge makes. Make it a PersistentAuth method that runs the same deviceCodeConfig as DeviceCodeChallenge, so the up-front check and the login cannot disagree.

Co-authored-by: Isaac <no-reply@databricks.com>
Group the cases so that a failing test name points at the broken step: preconditions, the successful login (with a host, from a saved profile and with --scopes), unsupported hosts, an unallowed client, and missing or wrong confirmation codes. Only the nominal test prints the device code requests, because the requested scope is what it checks; the unsupported host test checks that no device authorization request is made.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 419aeb611 | Workflow run

Regression test report

Selected 5 changed tests (limit=20, 0 not selected)

5 tests: regression=5 unreleased=0 coverage=0 failing=0

test branch main (edebd84) latest (v1.22.1)
TestAccept/cmd/auth/login/device-code/client-not-allowed ✅ ❌ ❌
TestAccept/cmd/auth/login/device-code/nominal ✅ ❌ ❌
TestAccept/cmd/auth/login/device-code/precondition ✅ ❌ ❌
TestAccept/cmd/auth/login/device-code/unsupported-host ✅ ❌ ❌
TestAccept/cmd/auth/login/device-code/wrong-code ✅ ❌ ❌
TestAccept/cmd/auth/login/device-code/client-not-allowed: branch ✅ | main (edebd84) ❌ | latest (v1.22.1) ❌

main (edebd84):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/client-not-allowed/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codeclient-not-allowed3544344225/002/output.txt
        @@ -1,3 +1,26 @@
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --client-id custom-client-id
        -Error: device authorization: Client is not allowed to use device code login (error code: unauthorized_client)
        +Error: unknown flag: --use-device-code
        +
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +

latest (v1.22.1): same output as main (edebd84)

TestAccept/cmd/auth/login/device-code/nominal: branch ✅ | main (edebd84) ❌ | latest (v1.22.1) ❌

main (edebd84):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/nominal/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codenominal2647541642/002/output.txt
        @@ -2,55 +2,29 @@
         === Login with a host
         
         >>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile test was successfully saved
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        ->>> [CLI] auth token --profile test
        -json.access_token = "oauth-token";
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
         
        ->>> [CLI] auth profiles
        -Name            Host                    Valid
        -test (Default)  [DATABRICKS_URL]  YES
         
        -=== Login again with the host from the saved profile
        -
        ->>> [CLI] auth login --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        -
        -And enter the code:
        -WDJB-MJHT
        -
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile test was successfully saved
        -
        ->>> [CLI] auth token --profile test
        -json.access_token = "oauth-token";
        -
        ->>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-2&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        -
        -=== Login with custom scopes
        -
        ->>> [CLI] auth login --host [DATABRICKS_URL] --profile scoped --use-device-code --scopes sql
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        -
        -And enter the code:
        -WDJB-MJHT
        -
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile scoped was successfully saved
        -
        ->>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+sql"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-3&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        +Exit code: 1
        
    acceptance_test.go:1282: Missing output file: out.databrickscfg

latest (v1.22.1):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/nominal/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codenominal667551265/002/output.txt
        @@ -2,55 +2,29 @@
         === Login with a host
         
         >>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile test was successfully saved
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        ->>> [CLI] auth token --profile test
        -json.access_token = "oauth-token";
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
         
        ->>> [CLI] auth profiles
        -Name            Host                    Valid
        -test (Default)  [DATABRICKS_URL]  YES
         
        -=== Login again with the host from the saved profile
        -
        ->>> [CLI] auth login --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        -
        -And enter the code:
        -WDJB-MJHT
        -
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile test was successfully saved
        -
        ->>> [CLI] auth token --profile test
        -json.access_token = "oauth-token";
        -
        ->>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-2&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        -
        -=== Login with custom scopes
        -
        ->>> [CLI] auth login --host [DATABRICKS_URL] --profile scoped --use-device-code --scopes sql
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        -
        -And enter the code:
        -WDJB-MJHT
        -
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Profile scoped was successfully saved
        -
        ->>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
        -{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+sql"}
        -{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-3&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
        +Exit code: 1
        
    acceptance_test.go:1282: Missing output file: out.databrickscfg
FAIL
TestAccept/cmd/auth/login/device-code/precondition: branch ✅ | main (edebd84) ❌ | latest (v1.22.1) ❌

main (edebd84):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/precondition/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codeprecondition2846836644/002/output.txt
        @@ -2,15 +2,107 @@
         === Device code login is disabled by default
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Error: --use-device-code is experimental. Set DATABRICKS_CLI_DEVICE_CODE=true to enable it
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Inputs device code login cannot honor are rejected
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --resource https://resource.test
        -Error: --use-device-code cannot be combined with --resource
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --skip-workspace
        -Error: --use-device-code cannot be combined with --skip-workspace
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         >>> musterr [CLI] auth login --profile scoped --use-device-code
        -Error: --use-device-code cannot be used with profile scoped because its login is scoped to resources. Log in without --use-device-code
        +Error: unknown flag: --use-device-code
        +
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +

latest (v1.22.1): same output as main (edebd84)

TestAccept/cmd/auth/login/device-code/unsupported-host: branch ✅ | main (edebd84) ❌ | latest (v1.22.1) ❌

main (edebd84):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/unsupported-host/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codeunsupported-host3713399232/002/output.txt
        @@ -2,30 +2,170 @@
         === No host
         
         >>> musterr [CLI] auth login --profile new-profile --use-device-code
        -Error: --use-device-code requires a workspace host. Pass it as an argument or with --host
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Account host
         
         >>> musterr [CLI] auth login --host https://accounts.cloud.databricks.test --profile test --use-device-code
        -Warn: [hostmetadata] failed to fetch host metadata for https://accounts.cloud.databricks.test, will skip for 1m0s
        -Error: --use-device-code is only supported for workspace hosts
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         >>> musterr [CLI] auth login --profile account --use-device-code
        -Error: --use-device-code is only supported for workspace hosts
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Unified host
         
         >>> DATABRICKS_DISCOVERY_URL=[DATABRICKS_URL]/oidc/accounts/spog-acct-123/.well-known/oauth-authorization-server musterr [CLI] auth login --host [DATABRICKS_URL] --use-device-code
        -Error: --use-device-code is only supported for workspace hosts
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Workspace without device code login
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Error: device code login is not enabled for this workspace
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         >>> musterr [CLI] auth login --profile workspace --use-device-code
        -Error: device code login is not enabled for this workspace
        +Error: unknown flag: --use-device-code
         
        +Usage:
        +  databricks auth login [PROFILE] [flags]
        +
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Device authorization requests
         
         >>> print_requests.py //oidc/v1/device/auth
        +File [OUTPUT_DIR]/out.requests.txt not found
        +
        +Exit code: 1

latest (v1.22.1): same output as main (edebd84)

TestAccept/cmd/auth/login/device-code/wrong-code: branch ✅ | main (edebd84) ❌ | latest (v1.22.1) ❌

main (edebd84):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/wrong-code/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codewrong-code1114281249/002/output.txt
        @@ -2,39 +2,85 @@
         === Login fails when no confirmation code is provided
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Error: reading confirmation code: EOF
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Login fails after 3 wrong confirmation codes
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left).
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left).
        -Confirmation code: Error: Invalid confirmation code (error code: invalid_request). Start login from scratch
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Login succeeds after re-entering the confirmation code
         
         >>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left).
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left).
        -Confirmation code: Profile test was successfully saved
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
        +Exit code: 1
        
FAIL

latest (v1.22.1):

acceptance_test.go:1311: Diff:
        --- cmd/auth/login/device-code/wrong-code/output.txt
        +++ /tmp/TestAcceptcmdauthlogindevice-codewrong-code2956156834/002/output.txt
        @@ -2,39 +2,85 @@
         === Login fails when no confirmation code is provided
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Error: reading confirmation code: EOF
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Login fails after 3 wrong confirmation codes
         
         >>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left).
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left).
        -Confirmation code: Error: Invalid confirmation code (error code: invalid_request). Start login from scratch
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
         
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
         === Login succeeds after re-entering the confirmation code
         
         >>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
        -Using a browser on another device, visit:
        -[DATABRICKS_URL]/device/verification
        +Error: unknown flag: --use-device-code
         
        -And enter the code:
        -WDJB-MJHT
        +Usage:
        +  databricks auth login [PROFILE] [flags]
         
        -Once finished, enter the confirmation code provided in your browser.
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left).
        -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left).
        -Confirmation code: Profile test was successfully saved
        +Flags:
        +      --client-id string       OAuth client ID to use for U2M authentication
        +      --configure-cluster      Prompts to configure cluster
        +      --configure-serverless   Prompts to configure serverless
        +  -h, --help                   help for login
        +      --resource stringArray   RFC 8707 resource indicator to scope the login to (repeatable). Requires --host.
        +      --scopes string          Comma-separated list of OAuth scopes to request (defaults to 'all-apis')
        +      --skip-workspace         Skip workspace selection for account-level access
        +      --timeout duration       Timeout for completing login challenge in the browser (default 1h0m0s)
        +
        +Global Flags:
        +      --account-id string     Databricks Account ID
        +      --debug                 enable debug logging
        +      --host string           Databricks Host
        +  -o, --output type           output type: text or json (default text)
        +  -p, --profile string        ~/.databrickscfg profile
        +  -t, --target string         bundle target to use (if applicable)
        +      --workspace-id string   Databricks Workspace ID
        +
        +
        +Exit code: 1

The prompt's handling of a timed-out context is covered by unit tests,
and keeping stdin open in the acceptance test cost 5 seconds per run.

Co-authored-by: Isaac <no-reply@databricks.com>

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Main things I'd look at first:

  1. Invalid device code errors get retried as if the confirmation code was wrong (device_code.go L117).
  2. DeviceAuth doesn't carry the login context, so --timeout and Ctrl-C don't stop a stalled request (L92).
  3. interval is ignored, which can hit the per device code 429 and end an approved login (L102).

The rest are smaller correctness gaps, a few convention points (error wrapping, duplicated error parsing, shared test setup) and cleanups. No new dependencies, and skipping a .nextchanges entry makes sense to me while the flag is hidden and env gated.

Comment thread libs/auth/u2m/device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go
Comment thread cmd/auth/login_device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go
Comment thread cmd/auth/login_device_code.go Outdated
Comment thread libs/auth/u2m/device_code.go Outdated
Comment thread acceptance/cmd/auth/login/device-code/unsupported-host/test.toml
Comment thread acceptance/cmd/auth/login/use-device-code-nominal/script Outdated
Comment thread libs/auth/u2m/device_code_test.go
maksim-zuev-db and others added 2 commits October 23, 2026 10:59
Take the context as an argument instead of using the one stored in
PersistentAuth, so that callers control the deadline and cancellation of
the device code login.

Co-authored-by: Isaac <no-reply@databricks.com>
oauth2.Config.DeviceAuth only uses the context to pick the HTTP client and
builds the request without it, so --timeout and Ctrl-C did not stop a
stalled device authorization request. Make the request directly, sharing
the request and error handling with the token exchange, and keep parsing
the response with oauth2.DeviceAuthResponse.

Co-authored-by: Isaac <no-reply@databricks.com>
maksim-zuev-db and others added 10 commits October 23, 2026 11:27
PersistentAuth only sends resource indicators on the browser authorization
request, so a device code login with WithResources would silently return
a token that is not scoped to them. The CLI already rejects --resource and
resource-scoped profiles; reject it in the library too.

Co-authored-by: Isaac <no-reply@databricks.com>
The arguments never carry a discovery URL before setHostAndAccountId runs,
so the first isAccountOrUnifiedHost call only checked for a classic account
host. Check for a classic account host, run host discovery, and check the
discovered URL for a unified host.

Co-authored-by: Isaac <no-reply@databricks.com>
The device code host check no longer calls it, so it is only used again to
decide whether setHostAndAccountId prompts for an account ID.

Co-authored-by: Isaac <no-reply@databricks.com>
Host discovery skips the discovery URL of a unified host whose metadata
has an {account_id} placeholder when no account ID is known. The device
code host check dropped the ?a= account ID from the host, so it treated
such a host as a workspace, while setHostAndAccountId later resolved it
as unified.

Co-authored-by: Isaac <no-reply@databricks.com>
Move the shared DATABRICKS_CLI_DEVICE_CODE setting and the home ignore
into a parent test.toml. The precondition test unsets the variable for
the case that checks the feature is disabled by default.

Co-authored-by: Isaac <no-reply@databricks.com>
The workspace answers an unknown device code with HTTP 400 invalid_request,
like a wrong confirmation code, so the CLI asks for the code again and the
error never has the "token exchange:" prefix. Also document the error for
too many token requests.

Co-authored-by: Isaac <no-reply@databricks.com>
A blank line asked for the confirmation code again without limit, so
anything that keeps sending newlines kept the login waiting until the
timeout. Send it like any other code instead: the workspace rejects an
empty confirmation code, and it counts as one of the 3 attempts.

Co-authored-by: Isaac <no-reply@databricks.com>
The host was checked separately for every place it can come from: the
flags, the profile picker and the profile. Split setHostAndAccountId into
setHost and setAccountID and check the resolved host once in between,
before setAccountID prompts for an account ID for account and unified
hosts. This reuses the host discovery of setHost instead of running it
again.

A workspace without device code login enabled is now rejected by
DeviceCodeChallenge, which runs without a prompt in between and fails
before creating a device code, so drop CheckDeviceCodeSupported.

An unsupported host given with --host now fails after the profile name
prompt instead of before it.

Co-authored-by: Isaac <no-reply@databricks.com>
oauthError rebuilt the error with %s, so errors.As could no longer find
the *httpclient.HttpError or *oauth2.RetrieveError behind an error from
DeviceCodeChallenge. Return an error type that shows the same message and
unwraps to the original error.

refresh() parsed the same RFC 6749 error response inline, with the same
message format and the same %s issue. Use the shared helpers there too,
and move them to error.go now that both login flows and the refresh use
them. An error response that is not JSON now returns the original error
instead of the JSON parsing error.

Co-authored-by: Isaac <no-reply@databricks.com>
The workspace answers an unknown device code, e.g. one it discarded after
the login expired, with HTTP 400 and invalid_request like a wrong
confirmation code. Entering the code again cannot succeed, so fail right
away. Only the error description tells the two apart; if it changes, the
CLI falls back to asking for the code again.

Co-authored-by: Isaac <no-reply@databricks.com>

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me, approving. The earlier threads are addressed, and the replies on the ones still open make sense to me (interval, keeping the single-shot token request, echo and EOF on the prompt), so feel free to resolve them.

I left a few small comments inline. None of them are blockers, but please take a look at them before merging.

Comment thread libs/auth/u2m/persistent_auth.go
Comment thread libs/auth/u2m/device_code.go
Comment thread cmd/auth/login_device_code.go Outdated
maksim-zuev-db and others added 3 commits October 23, 2026 14:13
Pin what refresh() returns through the default ApiClient transport for an
error response that is not JSON or has no error field. Before the error
handling was shared, these gave a JSON parsing error and an empty
"(error code: )" message.

Co-authored-by: Isaac <no-reply@databricks.com>
A 200 response without verification_uri or user_code made the CLI print
empty instructions and then wait for a confirmation code the user had no
way to get, and a missing device_code was sent as device_code=. Fail
before asking for the confirmation code if any of the three is empty.

Co-authored-by: Isaac <no-reply@databricks.com>
validateDeviceCodeHost repeated the check in needsAccountIDPrompt. Move it
into isAccountOrUnifiedHost and call it from both, so that the two cannot
drift apart.

Co-authored-by: Isaac <no-reply@databricks.com>

This branch has not been deployed

No deployments
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.

4 participants