Skip to content

Commit e482f4b

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 c422f35 commit e482f4b

6 files changed

Lines changed: 100 additions & 8 deletions

File tree

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
@@ -1047,7 +1047,7 @@ DESCRIPTION
10471047

10481048
## `shopify auth login`
10491049

1050-
Logs you in to your Shopify account.
1050+
Log in to a Shopify account.
10511051

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

10631076
## `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)