Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/brave-agents-login.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/cli': patch
'@shopify/cli-kit': patch
---

Improve Shopify CLI login guidance for agent-driven authentication flows.
34 changes: 34 additions & 0 deletions docs/cli/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Shopify CLI authentication

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.

## Supported flow

Shopify CLI currently supports user-driven device authentication:

1. Check whether a session is already available with `shopify auth status` or `shopify auth status --json`.
2. Run a command that requires authentication, or run `shopify auth login` directly.
3. Shopify CLI prints a verification URL and user code, or opens the verification URL in your browser.
4. The user completes login in the browser.
5. Keep the CLI process running. It polls for completion and continues automatically after authentication succeeds.

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.

## Commands

- `shopify auth login`: Start an interactive Shopify account login.
- `shopify auth logout`: Clear the stored Shopify CLI session.
- `shopify auth status`: Check whether Shopify CLI has a usable Shopify account session. Use `--json` for machine-readable output.
- Commands that need authentication may start the same login flow automatically.

## Non-interactive environments

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.

## Scopes

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.

## Support

For issues with Shopify CLI authentication, see https://shopify.dev/docs/api/shopify-cli or contact Shopify support at https://help.shopify.com.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {isTTY} from '../../../public/node/ui.js'
import {err, ok} from '../../../public/node/result.js'
import {AbortError} from '../../../public/node/error.js'
import {isCI} from '../../../public/node/system.js'
import {mockAndCaptureOutput} from '../../../public/node/testing/output.js'

import {beforeEach, describe, expect, test, vi} from 'vitest'
import {Response} from 'node-fetch'
Expand Down Expand Up @@ -66,6 +67,27 @@ describe('requestDeviceAuthorization', () => {
expect(got).toEqual(dataExpected)
})

test('prints explicit guidance for agents when the browser is not opened automatically', async () => {
// Given
const outputMock = mockAndCaptureOutput()
const response = new Response(JSON.stringify(data))
vi.mocked(shopifyFetch).mockResolvedValue(response)
vi.mocked(identityFqdn).mockResolvedValue('fqdn.com')
vi.mocked(clientId).mockReturnValue('clientId')
vi.mocked(isTTY).mockReturnValue(false)

// When
await requestDeviceAuthorization(['scope1', 'scope2'])

// Then
expect(outputMock.output()).toContain('User verification code: user_code')
expect(outputMock.output()).toContain('Open this link to start the auth process: verification_uri_complete')
expect(outputMock.output()).toContain('Waiting for authentication to complete. Keep this command running.')
expect(outputMock.output()).toContain(
'If you are an agent, show the URL and code to the user, ask them to complete login, then continue after this command finishes.',
)
})

test('when the response is not valid JSON, throw an error with context', async () => {
// Given
const response = new Response('not valid JSON')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,29 @@ export async function requestDeviceAuthorization(scopes: string[]): Promise<Devi
outputInfo(outputContent`User verification code: ${jsonResult.user_code}`)
const linkToken = outputToken.link(jsonResult.verification_uri_complete)

const cloudMessage = () => {
const waitingMessage = () => {
outputInfo('Waiting for authentication to complete. Keep this command running.')
outputInfo(
'If you are an agent, show the URL and code to the user, ask them to complete login, then continue after this command finishes.',
)
}

const manualLoginMessage = () => {
outputInfo(outputContent`👉 Open this link to start the auth process: ${linkToken}`)
waitingMessage()
}

if (isCloudEnvironment() || !isTTY()) {
cloudMessage()
manualLoginMessage()
} else {
outputInfo('👉 Press any key to open the login page on your browser')
await keypress()
const opened = await openURL(jsonResult.verification_uri_complete)
if (opened) {
outputInfo(outputContent`Opened link to start the auth process: ${linkToken}`)
waitingMessage()
} else {
cloudMessage()
manualLoginMessage()
}
}

Expand Down
17 changes: 15 additions & 2 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ DESCRIPTION

## `shopify auth login`

Logs you in to your Shopify account.
Log in to a Shopify account.

```
USAGE
Expand All @@ -1058,7 +1058,20 @@ FLAGS
--alias=<value> [env: SHOPIFY_FLAG_AUTH_ALIAS] Alias of the session you want to login to.

DESCRIPTION
Logs you in to your Shopify account.
Log in to a Shopify account.

Logs in to a Shopify account using a browser-based device authentication flow.

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.

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.

EXAMPLES
$ shopify auth login

$ shopify auth login --alias my-account
```

## `shopify auth logout`
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3042,8 +3042,13 @@
],
"args": {
},
"description": "Logs you in to your Shopify account.",
"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.",
"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.",
"enableJsonFlag": false,
"examples": [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --alias my-account"
],
"flags": {
"alias": {
"description": "Alias of the session you want to login to.",
Expand All @@ -3061,7 +3066,8 @@
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true
"strict": true,
"summary": "Log in to a Shopify account."
},
"auth:logout": {
"aliases": [
Expand Down
12 changes: 11 additions & 1 deletion packages/cli/src/cli/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import {Flags} from '@oclif/core'
import {outputCompleted} from '@shopify/cli-kit/node/output'

export default class Login extends Command {
static description = 'Logs you in to your Shopify account.'
static summary = 'Log in to a Shopify account.'

static descriptionWithMarkdown = `Logs in to a Shopify account using a browser-based device authentication flow.

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.

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.`

static description = this.descriptionWithoutMarkdown()

static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --alias my-account']

static flags = {
alias: Flags.string({
Expand Down
Loading