Skip to content

Commit d678b4b

Browse files
Improve auth guidance for agents
Clarify device-auth instructions in command output and docs so agents can guide users through browser login while keeping the CLI process running.
1 parent f700b48 commit d678b4b

6 files changed

Lines changed: 100 additions & 8 deletions

File tree

docs/cli/auth.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Shopify CLI authentication
2+
3+
Shopify CLI authenticates developers with Shopify through a device-code OAuth flow. This flow is designed to work in terminals, remote development environments, and agent-driven workflows.
4+
5+
## Supported flow
6+
7+
Shopify CLI currently supports user-driven device authentication:
8+
9+
1. Run a command that requires authentication, or run `shopify auth login` directly.
10+
2. Shopify CLI prints a verification URL and user code, or opens the verification URL in your browser.
11+
3. The user completes login in the browser.
12+
4. Keep the CLI process running. It polls for completion and continues automatically after authentication succeeds.
13+
14+
Agents should show the verification URL and user code to the user, ask the user to complete authentication in the browser, and wait for the CLI command to finish.
15+
16+
## Commands
17+
18+
- `shopify auth login`: Start an interactive Shopify account login.
19+
- `shopify auth logout`: Clear the stored Shopify CLI session.
20+
- Commands that need authentication may start the same login flow automatically.
21+
22+
## Non-interactive environments
23+
24+
In CI or fully non-interactive environments, use credentials provided through the supported environment variables for the command you are running. Do not start an interactive browser login from CI.
25+
26+
## Scopes
27+
28+
Shopify CLI requests the scopes needed for CLI workflows, including access to Shopify Admin, Partners, Storefront Renderer, Business Platform, and App Management APIs. Individual commands may request additional scopes for the task being performed.
29+
30+
## Support
31+
32+
For issues with Shopify CLI authentication, see https://shopify.dev/docs/api/shopify-cli or contact Shopify support at https://help.shopify.com.

packages/cli-kit/src/private/node/session/device-authorization.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {isTTY} from '../../../public/node/ui.js'
1212
import {err, ok} from '../../../public/node/result.js'
1313
import {AbortError} from '../../../public/node/error.js'
1414
import {isCI} from '../../../public/node/system.js'
15+
import {mockAndCaptureOutput} from '../../../public/node/testing/output.js'
1516

1617
import {beforeEach, describe, expect, test, vi} from 'vitest'
1718
import {Response} from 'node-fetch'
@@ -66,6 +67,27 @@ describe('requestDeviceAuthorization', () => {
6667
expect(got).toEqual(dataExpected)
6768
})
6869

70+
test('prints explicit guidance for agents when the browser is not opened automatically', async () => {
71+
// Given
72+
const outputMock = mockAndCaptureOutput()
73+
const response = new Response(JSON.stringify(data))
74+
vi.mocked(shopifyFetch).mockResolvedValue(response)
75+
vi.mocked(identityFqdn).mockResolvedValue('fqdn.com')
76+
vi.mocked(clientId).mockReturnValue('clientId')
77+
vi.mocked(isTTY).mockReturnValue(false)
78+
79+
// When
80+
await requestDeviceAuthorization(['scope1', 'scope2'])
81+
82+
// Then
83+
expect(outputMock.output()).toContain('User verification code: user_code')
84+
expect(outputMock.output()).toContain('Open this link to start the auth process: verification_uri_complete')
85+
expect(outputMock.output()).toContain('Waiting for authentication to complete. Keep this command running.')
86+
expect(outputMock.output()).toContain(
87+
'If you are an agent, show the URL and code to the user, ask them to complete login, then continue after this command finishes.',
88+
)
89+
})
90+
6991
test('when the response is not valid JSON, throw an error with context', async () => {
7092
// Given
7193
const response = new Response('not valid JSON')

packages/cli-kit/src/private/node/session/device-authorization.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,29 @@ export async function requestDeviceAuthorization(scopes: string[]): Promise<Devi
8181
outputInfo(outputContent`User verification code: ${jsonResult.user_code}`)
8282
const linkToken = outputToken.link(jsonResult.verification_uri_complete)
8383

84-
const cloudMessage = () => {
84+
const waitingMessage = () => {
85+
outputInfo('Waiting for authentication to complete. Keep this command running.')
86+
outputInfo(
87+
'If you are an agent, show the URL and code to the user, ask them to complete login, then continue after this command finishes.',
88+
)
89+
}
90+
91+
const manualLoginMessage = () => {
8592
outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`)
93+
waitingMessage()
8694
}
8795

8896
if (isCloudEnvironment() || !isTTY()) {
89-
cloudMessage()
97+
manualLoginMessage()
9098
} else {
9199
outputInfo('👉 Press any key to open the login page on your browser')
92100
await keypress()
93101
const opened = await openURL(jsonResult.verification_uri_complete)
94102
if (opened) {
95103
outputInfo(outputContent`Opened link to start the auth process: ${linkToken}`)
104+
waitingMessage()
96105
} else {
97-
cloudMessage()
106+
manualLoginMessage()
98107
}
99108
}
100109

packages/cli/README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ DESCRIPTION
10481048

10491049
## `shopify auth login`
10501050

1051-
Logs you in to your Shopify account.
1051+
Log in to a Shopify account.
10521052

10531053
```
10541054
USAGE
@@ -1058,7 +1058,20 @@ FLAGS
10581058
--alias=<value> [env: SHOPIFY_FLAG_AUTH_ALIAS] Alias of the session you want to login to.
10591059
10601060
DESCRIPTION
1061-
Logs you in to your Shopify account.
1061+
Log in to a Shopify account.
1062+
1063+
Logs in to a Shopify account using a browser-based device authentication flow.
1064+
1065+
If Shopify CLI prints a verification URL and user code, open the URL in a browser, complete login, and keep the
1066+
command running. The command continues automatically after authentication succeeds.
1067+
1068+
When running from an agent, show the verification URL and user code to the user, ask them to complete login in the
1069+
browser, and wait for the command to finish.
1070+
1071+
EXAMPLES
1072+
$ shopify auth login
1073+
1074+
$ shopify auth login --alias my-account
10621075
```
10631076

10641077
## `shopify auth logout`

packages/cli/oclif.manifest.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3042,8 +3042,13 @@
30423042
],
30433043
"args": {
30443044
},
3045-
"description": "Logs you in to your Shopify account.",
3045+
"description": "Logs in to a Shopify account using a browser-based device authentication flow.\n\nIf Shopify CLI prints a verification URL and user code, open the URL in a browser, complete login, and keep the command running. The command continues automatically after authentication succeeds.\n\nWhen running from an agent, show the verification URL and user code to the user, ask them to complete login in the browser, and wait for the command to finish.",
3046+
"descriptionWithMarkdown": "Logs in to a Shopify account using a browser-based device authentication flow.\n\nIf Shopify CLI prints a verification URL and user code, open the URL in a browser, complete login, and keep the command running. The command continues automatically after authentication succeeds.\n\nWhen running from an agent, show the verification URL and user code to the user, ask them to complete login in the browser, and wait for the command to finish.",
30463047
"enableJsonFlag": false,
3048+
"examples": [
3049+
"<%= config.bin %> <%= command.id %>",
3050+
"<%= config.bin %> <%= command.id %> --alias my-account"
3051+
],
30473052
"flags": {
30483053
"alias": {
30493054
"description": "Alias of the session you want to login to.",
@@ -3061,7 +3066,8 @@
30613066
"pluginAlias": "@shopify/cli",
30623067
"pluginName": "@shopify/cli",
30633068
"pluginType": "core",
3064-
"strict": true
3069+
"strict": true,
3070+
"summary": "Log in to a Shopify account."
30653071
},
30663072
"auth:logout": {
30673073
"aliases": [

packages/cli/src/cli/commands/auth/login.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ import {Flags} from '@oclif/core'
44
import {outputCompleted} from '@shopify/cli-kit/node/output'
55

66
export default class Login extends Command {
7-
static description = 'Logs you in to your Shopify account.'
7+
static summary = 'Log in to a Shopify account.'
8+
9+
static descriptionWithMarkdown = `Logs in to a Shopify account using a browser-based device authentication flow.
10+
11+
If Shopify CLI prints a verification URL and user code, open the URL in a browser, complete login, and keep the command running. The command continues automatically after authentication succeeds.
12+
13+
When running from an agent, show the verification URL and user code to the user, ask them to complete login in the browser, and wait for the command to finish.`
14+
15+
static description = this.descriptionWithoutMarkdown()
16+
17+
static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --alias my-account']
818

919
static flags = {
1020
alias: Flags.string({

0 commit comments

Comments
 (0)