Skip to content

Commit 08343c9

Browse files
committed
fix oauth refresh token flow
1 parent d28d716 commit 08343c9

6 files changed

Lines changed: 220 additions & 158 deletions

File tree

README.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,26 @@ Display the current version of the package.
174174
#### `datacustomcode configure`
175175
Configure credentials for connecting to Data Cloud.
176176

177+
**Prerequisites:**
178+
- A [connected app](#creating-a-connected-app) with OAuth settings configured
179+
- For OAuth Tokens authentication: [refresh token and core token](#obtaining-refresh-token-and-core-token)
180+
177181
Options:
178182
- `--profile TEXT`: Credential profile name (default: "default")
183+
- `--auth-type TEXT`: Authentication method (`oauth_tokens` or `username_password`, default: `oauth_tokens`)
184+
- `--login-url TEXT`: Salesforce login URL
185+
186+
For Username/Password authentication:
179187
- `--username TEXT`: Salesforce username
180188
- `--password TEXT`: Salesforce password
181189
- `--client-id TEXT`: Connected App Client ID
182190
- `--client-secret TEXT`: Connected App Client Secret
183-
- `--login-url TEXT`: Salesforce login URL
191+
192+
For OAuth Tokens authentication:
193+
- `--client-id TEXT`: Connected App Client ID
194+
- `--client-secret TEXT`: Connected App Client Secret
195+
- `--refresh-token TEXT`: OAuth refresh token (see [Obtaining Refresh Token](#obtaining-refresh-token-and-core-token))
196+
- `--core-token TEXT`: (Optional) OAuth core/access token - if not provided, it will be obtained using the refresh token
184197

185198

186199
#### `datacustomcode init`
@@ -324,6 +337,71 @@ You can read more about Jupyter Notebooks here: https://jupyter.org/
324337

325338
You now have all fields necessary for the `datacustomcode configure` command.
326339

340+
### Obtaining Refresh Token and Core Token
341+
342+
If you're using OAuth Tokens authentication (instead of Username/Password), follow these steps to obtain your refresh token and core token (access token).
343+
344+
#### Step 1: Note Connected App Details
345+
346+
From your connected app, note down the following:
347+
- **Client ID**
348+
- **Client Secret**
349+
- **Callback URL** (e.g., `http://localhost:55555/callback`)
350+
351+
#### Step 2: Obtain Authorization Code
352+
353+
1. Open a browser and navigate to the following URL (replace placeholders with your values):
354+
355+
```
356+
<LOGIN_URL>/services/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>
357+
```
358+
359+
2. After authenticating, you'll be redirected to your callback URL. The redirected URL will be in the form:
360+
```
361+
<CALLBACK_URL>?code=<CODE>
362+
```
363+
364+
3. Extract the `<CODE>` from the address bar. If the address bar doesn't show it, check the **Network tab** in your browser's developer tools.
365+
366+
#### Step 3: Exchange Code for Tokens
367+
368+
Make a POST request to exchange the authorization code for tokens. You can use `curl` or Postman:
369+
370+
```bash
371+
curl --location --request POST '<LOGIN_URL>/services/oauth2/token' \
372+
--header 'Content-Type: application/x-www-form-urlencoded' \
373+
--data-urlencode 'grant_type=authorization_code' \
374+
--data-urlencode 'code=<CODE>' \
375+
--data-urlencode 'client_id=<CLIENT_ID>' \
376+
--data-urlencode 'client_secret=<CLIENT_SECRET>' \
377+
--data-urlencode 'redirect_uri=<CALLBACK_URL>'
378+
```
379+
380+
The response will be a JSON object containing:
381+
382+
```json
383+
{
384+
"access_token": "<access_token>",
385+
"refresh_token": "<refresh_token>",
386+
"signature": "<signature>",
387+
"scope": "refresh_token cdp_query_api api cdp_profile_api cdp_api full",
388+
"id_token": "<id_token>",
389+
"instance_url": "https://your-instance.my.salesforce.com",
390+
"id": "https://login.salesforce.com/id/00DSB.../005SB...",
391+
"token_type": "Bearer",
392+
"issued_at": "1767743916187"
393+
}
394+
```
395+
396+
The key fields you need are:
397+
| Field | Description |
398+
|-------|-------------|
399+
| `access_token` | The **core token** (also called access token) |
400+
| `refresh_token` | The **refresh token** for obtaining new access tokens |
401+
| `instance_url` | Your Salesforce instance URL |
402+
403+
Use the `refresh_token` value when running `datacustomcode configure` with OAuth Tokens authentication.
404+
327405
## Other docs
328406

329407
- [Troubleshooting](./docs/troubleshooting.md)

src/datacustomcode/cli.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,33 @@ def _configure_username_password(
7070
)
7171

7272

73-
def _configure_oauth(
73+
def _configure_oauth_tokens(
7474
login_url: str,
7575
client_id: str,
7676
profile: str,
7777
) -> None:
78-
"""Configure credentials for OAuth authentication."""
78+
"""Configure credentials for OAuth Tokens authentication."""
7979
from datacustomcode.credentials import AuthType, Credentials
8080

8181
client_secret = click.prompt("Client Secret")
82+
refresh_token = click.prompt("Refresh Token")
83+
core_token = click.prompt(
84+
"Core Token (optional, press Enter to skip)",
85+
default="",
86+
show_default=False,
87+
)
8288

8389
credentials = Credentials(
8490
login_url=login_url,
8591
client_id=client_id,
86-
auth_type=AuthType.OAUTH,
92+
auth_type=AuthType.OAUTH_TOKENS,
8793
client_secret=client_secret,
94+
refresh_token=refresh_token,
95+
core_token=core_token if core_token else None,
8896
)
8997
credentials.update_ini(profile=profile)
9098
click.secho(
91-
f"OAuth credentials saved to profile '{profile}' successfully",
99+
f"OAuth Tokens credentials saved to profile '{profile}' successfully",
92100
fg="green",
93101
)
94102

@@ -97,12 +105,12 @@ def _configure_oauth(
97105
@click.option("--profile", default="default", help="Credential profile name")
98106
@click.option(
99107
"--auth-type",
100-
type=click.Choice(["oauth", "username_password"]),
108+
type=click.Choice(["oauth_tokens", "username_password"]),
101109
default=None,
102110
help="""Authentication method to use.
103111
104112
\b
105-
oauth - OAuth 2.0 client credentials flow (default, simplest)
113+
oauth_tokens - OAuth tokens (refresh_token/core_token) authentication
106114
username_password - Traditional username/password OAuth flow
107115
""",
108116
)
@@ -113,15 +121,15 @@ def configure(profile: str, auth_type: str) -> None:
113121
# If auth_type not specified, prompt for it
114122
if auth_type is None:
115123
click.echo("\nSelect authentication method:")
116-
click.echo(" 1. OAuth [default]")
124+
click.echo(" 1. OAuth Tokens [default]")
117125
click.echo(" 2. Username/Password")
118126
choice = click.prompt(
119127
"Enter choice",
120128
type=click.Choice(["1", "2"]),
121129
default="1",
122130
)
123131
auth_type_map = {
124-
"1": "oauth",
132+
"1": "oauth_tokens",
125133
"2": "username_password",
126134
}
127135
auth_type = auth_type_map[choice]
@@ -134,8 +142,8 @@ def configure(profile: str, auth_type: str) -> None:
134142
# Route to appropriate handler based on auth type
135143
if auth_type == AuthType.USERNAME_PASSWORD.value:
136144
_configure_username_password(login_url, client_id, profile)
137-
elif auth_type == AuthType.OAUTH.value:
138-
_configure_oauth(login_url, client_id, profile)
145+
elif auth_type == AuthType.OAUTH_TOKENS.value:
146+
_configure_oauth_tokens(login_url, client_id, profile)
139147

140148

141149
@cli.command()

0 commit comments

Comments
 (0)