Skip to content
Back to skills

075 Auth Setup 017cb681

ASecurity

This skill uses JWT Bearer authentication to access the Data Cloud Query API. Authentication is configured via an **External Client App (ECA)** in Salesforce.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
securitypythongobashsqlnodetestinggitapisecurity

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 075-auth-setup_017cb681 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 075 Auth Setup 017cb681?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 075 Auth Setup 017cb681
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-075-auth-setup-017cb681/badge)](https://www.skillsdirectory.com/skills/tools-only-075-auth-setup-017cb681)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Authentication Setup

This skill uses JWT Bearer authentication to access the Data Cloud Query API. Authentication is configured via an **External Client App (ECA)** in Salesforce.

## Prerequisites

1. **Salesforce CLI** installed and authenticated to your org
2. **OpenSSL** for certificate generation
3. **Data Cloud** enabled in your org
4. **System Administrator** profile (or appropriate permissions for ECA setup)

---

## Quick Setup (5 Steps)

### Step 1: Generate JWT Certificate

```bash
# Create directory for JWT keys
mkdir -p ~/.sf/jwt

# Generate private key and self-signed certificate (valid 1 year)
# Naming convention: {org_alias}-agentforce-observability.key
openssl req -x509 -sha256 -nodes -days 365 \
    -newkey rsa:2048 \
    -keyout ~/.sf/jwt/Vivint-DevInt-agentforce-observability.key \
    -out ~/.sf/jwt/Vivint-DevInt-agentforce-observability.crt \
    -subj "/CN=AgentforceObservability/O=Vivint"

# Secure the private key (required - Salesforce rejects world-readable keys)
chmod 600 ~/.sf/jwt/Vivint-DevInt-agentforce-observability.key
```

### Step 2: Create External Client App

In Salesforce Setup:

1. **Setup** → **External Client App Manager** → **New External Client App**
2. Fill in the basic details:

| Field | Value |
|-------|-------|
| Name | `Agentforce Observability` |
| API Name | `Agentforce_Observability` |
| Description | `JWT Bearer auth for Agentforce STDM extraction via Claude Code` |
| Distribution State | `Local` |

3. Click **Save**

### Step 3: Configure OAuth Settings

In the ECA → **OAuth Settings** tab:

| Setting | Value |
|---------|-------|
| Enable OAuth | ✅ Checked |
| Callback URL | `https://login.salesforce.com/services/oauth2/callback` |
| Selected OAuth Scopes | `cdp_query_api`, `refresh_token, offline_access` |
| Require PKCE | ❌ Unchecked (not needed for JWT Bearer) |
| Enable Client Credentials Flow | ❌ Optional |

**Upload Certificate:**
1. Check **Use digital signatures**
2. Click **Choose File**
3. Upload `~/.sf/jwt/{org}-agentforce-observability.crt`
4. Click **Save**

### Step 4: Configure App Policies

In the ECA → **Policies** tab:

| Setting | Value |
|---------|-------|
| Permitted Users | **Admin approved users are pre-authorized** |
| IP Relaxation | Relax IP restrictions (for CLI usage) |

**Add Your User:**
1. Click **Manage** → **Manage Profiles** or **Manage Permission Sets**
2. Add your user's profile or an appropriate permission set
3. Click **Save**

### Step 5: Get Consumer Key & Test

1. Go to ECA → **OAuth Settings** tab
2. Copy the **Consumer Key** (starts with `3MVG9...`)
3. Test authentication:

```bash
# Option 1: Pass consumer key directly
python3 scripts/cli.py test-auth \
    --org Vivint-DevInt \
    --consumer-key "3MVG9..."

# Option 2: Use environment variable
export SF_CONSUMER_KEY="3MVG9..."
python3 scripts/cli.py test-auth --org Vivint-DevInt
```

**Expected output:**
```
Testing Authentication
Org: Vivint-DevInt
Key: /Users/you/.sf/jwt/Vivint-DevInt-agentforce-observability.key

Getting org info...
Instance: https://vivint--devint.sandbox.my.salesforce.com
Username: your.user@vivint.com.devint
Sandbox: True

Testing token generation...
✓ Token generated

Testing Data Cloud access...
✓ Data Cloud accessible

Authentication successful!
```

---

## Key Path Resolution

The CLI resolves the private key path in this order:

1. **Explicit `--key-path`** argument (highest priority)
2. **App-specific key**: `~/.sf/jwt/{org_alias}-agentforce-observability.key`
3. **Generic org key**: `~/.sf/jwt/{org_alias}.key` (fallback)

### Examples

```bash
# Uses app-specific key automatically
python3 scripts/cli.py test-auth --org Vivint-DevInt --consumer-key "..."
# → Resolves to: ~/.sf/jwt/Vivint-DevInt-agentforce-observability.key

# Explicit key path (overrides all defaults)
python3 scripts/cli.py test-auth --org Vivint-DevInt \
    --consumer-key "..." \
    --key-path ~/.sf/jwt/custom-key.key
```

---

## File Locations

| File | Location | Description |
|------|----------|-------------|
| Private Key | `~/.sf/jwt/{org}-agentforce-observability.key` | RSA private key (chmod 600) |
| Certificate | `~/.sf/jwt/{org}-agentforce-observability.crt` | X.509 cert uploaded to Salesforce |
| Consumer Key | `$SF_CONSUMER_KEY` or `--consumer-key` | From ECA OAuth Settings |

---

## Required OAuth Scopes

| Scope | Purpose | Required |
|-------|---------|----------|
| `cdp_query_api` | Execute Data Cloud SQL queries | ✅ Yes |
| `refresh_token, offline_access` | Server-to-server access | ✅ Yes |
| `cdp_profile_api` | Access profile and DMO metadata | Optional |

---

## Troubleshooting

### invalid_grant Error

```
RuntimeError: Token exchange failed: invalid_grant
```

**Causes & Fixes:**

| Cause | Fix |
|-------|-----|
| Certificate mismatch | Re-upload `.crt` file to ECA |
| Expired certificate | Regenerate with `openssl` (see Step 1) |
| User not authorized | Add user to ECA policies (see Step 4) |
| Wrong login URL | Verify sandbox detection is working |

**Verify certificate expiry:**
```bash
openssl x509 -enddate -noout -in ~/.sf/jwt/Vivint-DevInt-agentforce-observability.crt
```

### 403 Forbidden

```
RuntimeError: Access denied: Ensure ECA has cdp_query_api scope
```

**Fix:** Add `cdp_query_api` scope to the ECA OAuth Settings.

### Key Not Found

```
FileNotFoundError: Private key not found at ~/.sf/jwt/myorg.key
```

**Causes:**
1. Key file doesn't exist → Generate with Step 1
2. Wrong org alias → Check `sf org list` for correct alias
3. Using old naming convention → Rename to `{org}-agentforce-observability.key`

### Permission Denied on Key

```
PermissionError: [Errno 13] Permission denied: '~/.sf/jwt/...'
```

**Fix:**
```bash
chmod 600 ~/.sf/jwt/*.key
```

---

## Sandbox vs Production

The skill automatically detects sandbox orgs:

1. Reads `isSandbox` from `sf org display --json`
2. Uses `https://test.salesforce.com` for token exchange (sandboxes)
3. Uses `https://login.salesforce.com` for production

No additional configuration needed.

---

## Security Best Practices

1. **Protect private keys**: Always `chmod 600 ~/.sf/jwt/*.key`
2. **Rotate certificates annually**: Regenerate before expiration
3. **Use separate ECAs**: One per org/environment for isolation
4. **Never commit keys**: Add `*.key` to `.gitignore`
5. **Audit access**: Review ECA usage in Setup → Security → Connected Apps OAuth Usage

---

## Environment Variables

For automation/CI, set these environment variables:

```bash
# Consumer key (org-specific takes priority)
export SF_VIVINT_DEVINT_CONSUMER_KEY="3MVG9..."
# or generic fallback
export SF_CONSUMER_KEY="3MVG9..."
```

The CLI checks in this order:
1. `--consumer-key` argument
2. `SF_{ORG_ALIAS}_CONSUMER_KEY` (uppercase, hyphens to underscores)
3. `SF_CONSUMER_KEY`

---

## See Also

- [sf-connected-apps skill](../../sf-connected-apps/SKILL.md) - General ECA patterns
- [CLI Reference](cli-reference.md) - All command options
- [Troubleshooting](../resources/troubleshooting.md) - More error solutions

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.