Skip to content

Commit f9c8f4b

Browse files
authored
Document public-client (PKCE) OAuth flow and CLI browser login (#1031)
DNSimple introduced a [new OAuth mechanism for the CLI](dnsimple/cli#42), along with changes to the OAuth flow itself. This updates the customer-facing developer docs to match. On the server, the OAuth provider now supports **public clients** that authenticate with **PKCE** (RFC 7636, `S256`) instead of a `client_secret`, plus **loopback redirect URIs** with dynamic ports (RFC 8252 §7.3) for native and CLI apps. On the CLI side, `dnsimple auth login` now **defaults to an interactive browser login**, with API token login available via `--with-token`. Related to dnsimple/dnsimple-support#1984
1 parent dd6bbd9 commit f9c8f4b

2 files changed

Lines changed: 76 additions & 18 deletions

File tree

content/cli.md

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ Create your [API token](https://support.dnsimple.com/articles/api-access-token/)
1919
- Production tokens: [dnsimple.com/user](https://dnsimple.com/user)
2020
- Sandbox tokens: [sandbox.dnsimple.com/user](https://sandbox.dnsimple.com/user)
2121

22-
Install the CLI, using one of the methods below. Then run `dnsimple auth login` to login using the auth token. If you are logging into a Sandbox environment, run `dnsimple auth login --sandbox`.
22+
Install the CLI, using one of the methods below. Then run `dnsimple auth login` to authenticate. If you are logging into a Sandbox environment, run `dnsimple auth login --sandbox`.
2323

24-
The CLI currently supports API token authentication, including classic and scoped API tokens.
24+
You can authenticate in two ways:
25+
26+
- **Browser login** (default): run `dnsimple auth login` to authorize the CLI in your browser, with no token to copy.
27+
- **API token**: run `dnsimple auth login --with-token` to paste or pipe a [classic or scoped API token](https://support.dnsimple.com/articles/api-access-token/).
2528

2629
## Installation
2730

@@ -92,15 +95,45 @@ The command prints an AI-friendly description of available commands, flags, and
9295

9396
## Authentication
9497

95-
The CLI uses authentication contexts. A context stores the API token, account, and environment for later commands. You can keep multiple contexts, such as one for production and one for sandbox, and switch between them.
98+
The CLI uses authentication contexts. A context stores your credential, account, and environment for later commands. You can keep multiple contexts, such as one for production and one for sandbox, and switch between them.
99+
100+
There are two ways to log in. Browser login is the default; API token login is available with `--with-token`. Both store the result as a context.
101+
102+
### Browser login
103+
104+
By default, `dnsimple auth login` authenticates in your browser. The CLI opens the DNSimple authorization page; once you approve, it completes the login automatically, with no token to copy or paste.
96105

97106
```shell
98-
# Log in to production.
107+
# Log in to production in the browser.
99108
dnsimple auth login
100109

101-
# Log in to sandbox and store it as a separate context.
110+
# Log in to sandbox in the browser.
102111
dnsimple auth login --sandbox
112+
```
113+
114+
Browser login uses the OAuth 2.0 Authorization Code grant with PKCE and a loopback redirect; see the [OAuth documentation](/v2/oauth/) for the underlying flow. If the CLI cannot open a browser, for example on a machine with no display server, it prints the authorization URL so you can open it manually and keeps waiting for the callback.
115+
116+
Browser login runs only on an interactive terminal. When standard input is not a terminal, for example a token piped in CI, `dnsimple auth login` reads an API token from stdin instead (see [Token login](#token-login)).
117+
118+
### Token login
119+
120+
To authenticate with an API token instead of the browser, pass `--with-token`. On a terminal it prompts you to paste the token, with the input hidden; with piped input it reads the token from stdin. The CLI supports classic and scoped [API tokens](https://support.dnsimple.com/articles/api-access-token/).
121+
122+
```shell
123+
# Paste a token when prompted (input is hidden).
124+
dnsimple auth login --with-token
125+
126+
# Pipe a token from stdin, for example in CI.
127+
printf '%s' "$DNSIMPLE_TOKEN" | dnsimple auth login --with-token --name ci
128+
```
129+
130+
You can also pipe a token to a plain `dnsimple auth login`: when standard input is not a terminal, it reads the token from stdin without `--with-token`.
103131

132+
### Managing contexts
133+
134+
List, switch, and inspect stored contexts:
135+
136+
```shell
104137
# List stored contexts. The active context is marked with *.
105138
dnsimple auth list
106139

@@ -118,12 +151,6 @@ dnsimple auth login --name work
118151
dnsimple auth login --sandbox --name sandbox-work
119152
```
120153

121-
For scripts or machines where interactive prompts are not convenient, read a token from standard input:
122-
123-
```shell
124-
printf '%s' "$DNSIMPLE_TOKEN" | dnsimple auth login --with-token --name ci
125-
```
126-
127154
Remove a stored context when you no longer need it:
128155

129156
```shell
@@ -183,6 +210,8 @@ dnsimple auth login --sandbox --name sandbox
183210
dnsimple --context sandbox domains list
184211
```
185212

213+
Browser login works here too: `dnsimple auth login --sandbox` opens the Sandbox authorization page on `sandbox.dnsimple.com`.
214+
186215
You can also make one command use Sandbox without storing a context:
187216

188217
```shell

content/v2/oauth.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@ excerpt: This page documents the OAuth 2 flow you can use to access the DNSimple
1212

1313
DNSimple supports [OAuth 2.0](http://oauth.net/) for authenticating API requests. OAuth 2 is a protocol that lets external applications request authorization to private details in a user's DNSimple account without getting their password. This is preferred over Basic Authentication because tokens can be revoked by users at any time.
1414

15-
Developers need to register their application before getting started. A registered OAuth application is assigned a unique client ID and client secret. The client secret should not be shared.
15+
Developers need to register their application before getting started. A registered OAuth application is assigned a unique client ID, and the way it authenticates depends on its type:
16+
17+
- **Confidential clients** are server-side web apps that can keep a secret. They are assigned a client ID and a client secret, and authenticate at the token endpoint with that secret. The client secret should not be shared.
18+
- **Public clients** are native, mobile, single-page, or command-line apps that cannot keep a secret. They authenticate with [PKCE](https://www.rfc-editor.org/rfc/rfc7636) (Proof Key for Code Exchange) instead of a client secret. See [Native and CLI apps](#native-and-cli-apps-loopback-redirect-uris).
19+
20+
> [!NOTE]
21+
> If you do not see a client type choice when you register an application, your account registers web apps (confidential clients) by default. Public clients can be enabled on request; contact DNSimple support to register one for your account.
1622
1723
## Web Application Flow
1824

1925
This is a description of the OAuth 2 flow for authorizing users from 3rd party web sites. The OAuth 2 specification is described in the [RFC 6749](http://tools.ietf.org/html/rfc6749#section-4).
2026

2127
> [!NOTE]
2228
> The API currently supports only the OAuth 2 [Authorization Code Grant](http://tools.ietf.org/html/rfc6749#section-4.1) flow. Therefore, [the only supported value for the `response_type` is `code`](http://tools.ietf.org/html/rfc6749#section-3.1.1).
29+
>
30+
> Public clients must secure the flow with [PKCE](https://www.rfc-editor.org/rfc/rfc7636) (Proof Key for Code Exchange). The only accepted `code_challenge_method` is `S256`; the `plain` method is not supported.
2331
2432
### Step 1 - Authorization
2533

@@ -37,8 +45,10 @@ The following values should be passed as GET parameters:
3745
`client_id` | **Required**. The client ID you received from DNSimple when you registered the application.
3846
`state` | **Required**. An unguessable random string. It is used to protect against cross-site request forgery attacks and it will be passed back to your redirect URI.
3947
`account_id` | Automatically select a preferred DNSimple account ID when the user arrives at `/oauth/authorize`. If the account is already authorized, the user will be redirected to your `redirect_uri` as a successful request.
40-
`redirect_uri` | Where to redirect the user after authorization has completed. This must be the exact URI registered or a subdirectory.
48+
`redirect_uri` | Where to redirect the user after authorization has completed. This must be the exact URI registered or a subdirectory. Native and CLI apps can register a [loopback redirect URI](#native-and-cli-apps-loopback-redirect-uris) whose port is matched leniently.
4149
`scope` | We currently don't use this field.
50+
`code_challenge` | **Required for public clients** ([PKCE](https://www.rfc-editor.org/rfc/rfc7636)). The base64url-encoded SHA-256 digest of your code verifier. Optional for confidential clients, where it adds defense-in-depth.
51+
`code_challenge_method` | **Required when `code_challenge` is present**. Must be `S256`.
4252

4353
Because `/oauth/authorize` is a website, there is no direct return value. However, after the user authorizes your app, they will be sent to your redirect URI.
4454

@@ -65,10 +75,11 @@ The following values should be passed as POST parameters:
6575

6676
`grant_type` | **Required**. The grant type requested. We currently only support `authorization_code`.
6777
`client_id` | **Required**. The client ID you received from DNSimple when you registered the application.
68-
`client_secret` | **Required**. The client secret you received from DNSimple when you registered the application.
78+
`client_secret` | **Required for confidential clients**. The client secret you received from DNSimple when you registered the application. Ignored for public clients.
79+
`code_verifier` | **Required for public clients** ([PKCE](https://www.rfc-editor.org/rfc/rfc7636)). The code verifier whose SHA-256 challenge matches the `code_challenge` sent to `/oauth/authorize`. Ignored for confidential clients.
6980
`code` | **Required**. The code acquired in the previous authorization step.
70-
`redirect_uri` | **Required**. Only used to validate that it matches the original `/oauth/authorize`, not used to redirect again.
71-
`state` | **Required**. The state content originally passed to `/oauth/authorize`.
81+
`redirect_uri` | **Required if it was included in the authorization request**. Only used to validate that it matches the value sent to the original `/oauth/authorize`, not used to redirect again.
82+
`state` | **Required if it was included in the authorization request**. The state content originally passed to `/oauth/authorize`.
7283

7384
You'll receive a JSON response. If the request is successful, the response will include an access token, the token type and the account ID. The token type will always be `bearer`.
7485

@@ -82,9 +93,9 @@ You'll receive a JSON response. If the request is successful, the response will
8293

8394
If the request fails, you'll receive a JSON response with an `error` code and an `error_description`. The HTTP status and the `error` value depend on the failure:
8495

85-
`invalid_grant` | **HTTP 400**. The authorization `code` is unknown or expired, or it was not issued to the supplied `client_id`.
96+
`invalid_grant` | **HTTP 400**. The authorization `code` is unknown or expired, or it was not issued to the supplied `client_id`. For public clients, this is also returned when the PKCE `code_verifier` does not match the `code_challenge` from the authorization request.
8697
`invalid_client` | **HTTP 401**. Client authentication failed, for example an incorrect `client_secret`.
87-
`invalid_request` | **HTTP 400**. The request is otherwise invalid, for example an unsupported `grant_type` or a `redirect_uri` / `state` that does not match the original authorization request.
98+
`invalid_request` | **HTTP 400**. The request is otherwise invalid, for example an unsupported `grant_type`, a `redirect_uri` / `state` that does not match the original authorization request, or a public-client authorization request with a missing or invalid PKCE `code_challenge`.
8899

89100
### Step 3 - API authentication
90101

@@ -102,3 +113,21 @@ $ curl -H "Authorization: Bearer ACCESS-TOKEN" https://api.dnsimple.com/v2/whoam
102113

103114
> [!NOTE]
104115
> If you are using the [sandbox environment](/sandbox/) replace `dnsimple.com` with `sandbox.dnsimple.com`.
116+
117+
## Native and CLI apps (loopback redirect URIs)
118+
119+
Native applications such as command-line tools and desktop apps are public clients: they cannot keep a client secret, so they secure the flow with [PKCE](https://www.rfc-editor.org/rfc/rfc7636) and receive the authorization `code` on a loopback callback running on the local machine.
120+
121+
Register a loopback callback URL as your application's redirect URI:
122+
123+
```
124+
http://127.0.0.1/callback
125+
http://[::1]/callback
126+
```
127+
128+
A native app binds its callback server to an ephemeral port chosen at runtime, so it cannot register that port in advance. As described in [RFC 8252 section 7.3](https://www.rfc-editor.org/rfc/rfc8252#section-7.3), DNSimple ignores the port of a loopback redirect URI when both the registered URI and the runtime URI use a loopback IP literal. Your app can therefore listen on any available port, for example `http://127.0.0.1:53241/callback`, and the redirect URI will still match.
129+
130+
> [!NOTE]
131+
> Port leniency applies only to the loopback IP literals `127.0.0.1` and `[::1]`. The hostname `localhost` is matched strictly, including its port, so use an IP literal if you need a dynamic port.
132+
133+
The [DNSimple CLI](/cli/) uses exactly this flow: it is a public client that runs the Authorization Code grant with PKCE and a loopback callback to log you in through your browser.

0 commit comments

Comments
 (0)