Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5d22860
Add experimental device code login (auth login --use-device-code)
maksim-zuev-db Oct 15, 2026
839cee7
Add tests for device code login
maksim-zuev-db Oct 15, 2026
55c9899
Only retry device code confirmation on HTTP 400 invalid_request
maksim-zuev-db Oct 19, 2026
b4ff455
Describe device code login as experimental instead of a proof of concept
maksim-zuev-db Oct 22, 2026
4fea011
Rename needsAccountIDPrompt to isAccountOrUnifiedHost
maksim-zuev-db Oct 22, 2026
eb74444
Share the device code support check with DeviceCodeChallenge
maksim-zuev-db Oct 22, 2026
cbd3d8f
Split the device code login acceptance test by user journey
maksim-zuev-db Oct 22, 2026
556ea93
Remove the device code step from the login timeout acceptance test
maksim-zuev-db Oct 22, 2026
ca42f51
Update docs
maksim-zuev-db Oct 23, 2026
71f55db
Pass the context to DeviceCodeChallenge
maksim-zuev-db Oct 23, 2026
ae65a33
Send the device authorization request with the login context
maksim-zuev-db Oct 23, 2026
c0421b8
Reject resource indicators in device code login
maksim-zuev-db Oct 23, 2026
817d903
Simplify the device code host check
maksim-zuev-db Oct 23, 2026
87a2d5b
Revert the rename of needsAccountIDPrompt
maksim-zuev-db Oct 23, 2026
1662882
Use the account ID from the host in the device code host check
maksim-zuev-db Oct 23, 2026
05cdba6
Group the device code acceptance tests under device-code/
maksim-zuev-db Oct 23, 2026
89b41a9
Document the device code errors the workspace returns
maksim-zuev-db Oct 23, 2026
9b63504
Count a blank confirmation code as a wrong attempt
maksim-zuev-db Oct 23, 2026
ea2e74f
Check the device code host once, after it is resolved
maksim-zuev-db Oct 23, 2026
388bc30
Share OAuth error response handling and keep the cause
maksim-zuev-db Oct 23, 2026
5d31ef5
Do not retry an unknown device code
maksim-zuev-db Oct 23, 2026
7eb52b5
Test refresh errors without an OAuth error code
maksim-zuev-db Oct 23, 2026
9f8fe2d
Reject an incomplete device authorization response
maksim-zuev-db Oct 23, 2026
419aeb6
Share the account or unified host check with device code login
maksim-zuev-db Oct 23, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

>>> 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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
sethome "./home"

trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --client-id custom-client-id
16 changes: 16 additions & 0 deletions acceptance/cmd/auth/login/device-code/nominal/out.databrickscfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified.
[DEFAULT]

[test]
host = [DATABRICKS_URL]
workspace_id = [WORKSPACE_ID]
auth_type = databricks-cli

[__settings__]
default_profile = test

[scoped]
host = [DATABRICKS_URL]
workspace_id = [WORKSPACE_ID]
scopes = sql
auth_type = databricks-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
56 changes: 56 additions & 0 deletions acceptance/cmd/auth/login/device-code/nominal/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

=== 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

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";

>>> [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"}
20 changes: 20 additions & 0 deletions acceptance/cmd/auth/login/device-code/nominal/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
sethome "./home"

title "Login with a host\n"
printf "FTFA-QQKY\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
trace $CLI auth token --profile test | gron.py | grep access_token
trace $CLI auth profiles

title "Login again with the host from the saved profile\n"
printf "FTFA-QQKY\n" | trace $CLI auth login --profile test --use-device-code
trace $CLI auth token --profile test | gron.py | grep access_token
trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token

# The token gets the scopes requested at device authorization, so --scopes is
# sent there and not with the token request.
title "Login with custom scopes\n"
printf "FTFA-QQKY\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile scoped --use-device-code --scopes sql
trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token

# Track the .databrickscfg file that was created to surface changes.
mv "./home/.databrickscfg" "./out.databrickscfg"
1 change: 1 addition & 0 deletions acceptance/cmd/auth/login/device-code/nominal/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RecordRequests = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[scoped]
host = $DATABRICKS_HOST
auth_type = databricks-cli
resources = https://resource.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
16 changes: 16 additions & 0 deletions acceptance/cmd/auth/login/device-code/precondition/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

=== 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

=== 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

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --skip-workspace
Error: --use-device-code cannot be combined with --skip-workspace

>>> 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
15 changes: 15 additions & 0 deletions acceptance/cmd/auth/login/device-code/precondition/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
sethome "./home"

title "Device code login is disabled by default\n"
(
unset DATABRICKS_CLI_DEVICE_CODE
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
)

title "Inputs device code login cannot honor are rejected\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --resource https://resource.test
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --skip-workspace

# A profile scoped to resources is rejected like --resource.
envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg"
trace musterr $CLI auth login --profile scoped --use-device-code
4 changes: 4 additions & 0 deletions acceptance/cmd/auth/login/device-code/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Env.DATABRICKS_CLI_DEVICE_CODE = "true"
Ignore = [
"home"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
host = $DATABRICKS_HOST
auth_type = databricks-cli

[account]
host = https://accounts.cloud.databricks.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

=== 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

=== 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

>>> musterr [CLI] auth login --profile account --use-device-code
Error: --use-device-code is only supported for workspace hosts

=== 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

=== 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

>>> musterr [CLI] auth login --profile workspace --use-device-code
Error: device code login is not enabled for this workspace

=== Device authorization requests

>>> print_requests.py //oidc/v1/device/auth
27 changes: 27 additions & 0 deletions acceptance/cmd/auth/login/device-code/unsupported-host/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
sethome "./home"

title "No host\n"
# The profile does not exist yet and no host is given. auth login does not read
# DATABRICKS_HOST, so the usual login would fall back to the browser-based
# discovery flow, which device code login does not support.
trace musterr $CLI auth login --profile new-profile --use-device-code

envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg"

# The host is rejected before the CLI would prompt for an account ID, whether
# it is passed directly or comes from a profile without one.
title "Account host\n"
trace musterr $CLI auth login --host https://accounts.cloud.databricks.test --profile test --use-device-code
trace musterr $CLI auth login --profile account --use-device-code

# An account-scoped discovery URL marks the host as a unified host.
title "Unified host\n"
trace DATABRICKS_DISCOVERY_URL="$DATABRICKS_HOST/oidc/accounts/spog-acct-123/.well-known/oauth-authorization-server" musterr $CLI auth login --host $DATABRICKS_HOST --use-device-code

title "Workspace without device code login\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
trace musterr $CLI auth login --profile workspace --use-device-code

# Every host is rejected before the device authorization request.
title "Device authorization requests\n"
trace print_requests.py //oidc/v1/device/auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
RecordRequests = true

# Workspaces that are not enrolled in the device code preview do not advertise
# a device_authorization_endpoint in their OIDC discovery document.
[[Server]]
Comment thread
maksim-zuev-db marked this conversation as resolved.
Pattern = "GET /oidc/.well-known/oauth-authorization-server"
Response.Body = '''
{
"authorization_endpoint": "https://workspace.test/oidc/v1/authorize",
"token_endpoint": "https://workspace.test/oidc/v1/token"
}
'''
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
40 changes: 40 additions & 0 deletions acceptance/cmd/auth/login/device-code/wrong-code/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

=== 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

And enter the code:
WDJB-MJHT

Once finished, enter the confirmation code provided in your browser.
Confirmation code: Error: reading confirmation code: EOF

=== 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

And enter the code:
WDJB-MJHT

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

=== 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

And enter the code:
WDJB-MJHT

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
13 changes: 13 additions & 0 deletions acceptance/cmd/auth/login/device-code/wrong-code/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
sethome "./home"

title "Login fails when no confirmation code is provided\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code < /dev/null

title "Login fails after 3 wrong confirmation codes\n"
printf "AAAA-AAAA\nBBBB-BBBB\nCCCC-CCCC\n" | trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code

# A wrong confirmation code leaves the device code valid, so the user can
# enter it again. A blank line counts as a wrong confirmation code.
# The confirmation code is accepted the way the browser displays it, in any case.
title "Login succeeds after re-entering the confirmation code\n"
printf "AAAA-AAAA\n\nftfa-qqky\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
55 changes: 55 additions & 0 deletions cmd/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,58 @@ sequenceDiagram
CLI -X User: no auth configured
end
```

# Device code login (experimental)

`databricks auth login --use-device-code` logs in from a machine without a browser.
It is experimental: the flag is hidden and requires
`DATABRICKS_CLI_DEVICE_CODE=true`. Only workspace hosts are supported. See
[docs/device-code-login.md](../../docs/device-code-login.md) for the user guide.

Databricks implements a variant of [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628):
instead of polling the token endpoint until the user approves the login, the
browser shows the user a confirmation code, which the CLI sends together with
the device code in a single token request.

```mermaid
sequenceDiagram
autonumber
actor User

User ->> CLI: type `databricks auth login HOST --use-device-code`
CLI ->>+ HOST: request OIDC endpoints
HOST ->>- CLI: token & device_authorization endpoints
CLI ->>+ Device Auth Endpoint: client_id + scopes
Device Auth Endpoint ->>- CLI: DEVICE CODE + USER CODE + verification URI
CLI ->> User: show verification URI + USER CODE

User ->>+ Verification Page: open on another device, go through SSO
User ->> Verification Page: enter USER CODE and approve
Verification Page ->>- User: CONFIRMATION CODE

User ->> CLI: type CONFIRMATION CODE
CLI ->>+ Token Endpoint: Exchange: DEVICE CODE + CONFIRMATION CODE
Token Endpoint ->>- CLI: Access Token (JWT) + refresh + expiry
CLI ->> Token cache: Save Access Token (JWT) + refresh + expiry
CLI ->> User: success
```

Notes on the server behavior the CLI relies on:

- Workspaces advertise `device_authorization_endpoint` in
`/oidc/.well-known/oauth-authorization-server` only when device code login
is enabled for them. The CLI fails before requesting a device code if it is
missing.
- The token request carries the confirmation code in the `confirmation_code`
form parameter. A wrong confirmation code does not consume the device code;
a successful exchange does. The server answers a wrong confirmation code
with HTTP 400 and `invalid_request`, and the CLI lets the user enter it up
to 3 times in total. Any other error ends the login, including an unknown
device code, which the server answers the same way but with the
description `Invalid device code`.
- Approving the login in the browser extends the device code's lifetime, so
the CLI does not enforce the `expires_in` from the device authorization
response.
- The refresh token is single-use (rotating) and has an absolute lifetime of
24 hours by default. After that, `databricks auth token` fails with a
refresh error and the user needs to log in again.
Loading
Loading