Skip to content

Commit f8274d0

Browse files
committed
@W-22151509 Add JWT Bearer authentication for certificate-based OAuth
1 parent bad9034 commit f8274d0

20 files changed

Lines changed: 1598 additions & 44 deletions

docs/guide/authentication.md

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,22 @@ Most CLI operations require an Account Manager API Client. This is configured in
3636

3737
### Authentication Methods
3838

39-
The CLI supports four authentication methods:
39+
The CLI supports five authentication methods:
4040

4141
| Method | When Used | Role Configuration |
4242
| ---------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------- |
4343
| **User Authentication** | When `--user-auth` is passed, or when only a client ID is provided (no secret) | Roles configured on your **user account** |
4444
| **Client Credentials** | When both `--client-id` and `--client-secret` are provided | Roles configured on the **API client** |
45+
| **JWT Bearer** | When `--jwt-cert` and `--jwt-key` are provided (certificate-based authentication) | Roles configured on the **API client** |
4546
| **Stateful User Authentication** | After running `b2c auth login` — browser-based login, token stored and reused | Roles configured on your **user account** |
4647
| **Stateful Client Authentication** | After running `b2c auth client` — client credentials login, token stored and reused | Roles configured on the **API client** |
4748

4849
**User Authentication** opens a browser for interactive login and uses roles assigned to your user account. This is ideal for development and manual operations. Use `--user-auth` as a shorthand for `--auth-methods implicit` on any OAuth command.
4950

5051
**Client Credentials** uses the API client's secret for non-interactive authentication. This is ideal for CI/CD pipelines and automation.
5152

53+
**JWT Bearer** uses a public/private certificate pair for secure authentication without storing client secrets. This is ideal for production environments and CI/CD where you want stronger security. See [JWT Authentication](#jwt-authentication-certificate-based) for details.
54+
5255
**Stateful User Auth** uses `b2c auth login` to open a browser for interactive login once, then stores the session on disk. Subsequent commands automatically use the stored token when it is present and valid, without re-opening the browser. Clear the session with `b2c auth logout`. See [Auth Commands](/cli/auth#b2c-auth-login) for details.
5356

5457
**Stateful Client Auth** uses `b2c auth client` to authenticate once with client credentials (or user/password), store the session, and reuse it across subsequent commands without passing credentials each time. Mirrors the [sfcc-ci](https://github.com/SalesforceCommerceCloud/sfcc-ci) `client:auth` workflow. Use `--renew` to enable automatic token renewal via `b2c auth client renew`. See [Auth Commands](/cli/auth#b2c-auth-client) for details.
@@ -75,9 +78,14 @@ For Account Manager operations that require user-level roles (organization and A
7578
- **Password**: A strong client secret (save this securely for Client Credentials auth)
7679
5. Configure the **Token Endpoint Auth Method**:
7780
- `client_secret_basic` for client credentials flow
81+
- `private_key_jwt` for JWT Bearer authentication (certificate-based)
82+
83+
::: tip Certificate-Based Authentication
84+
For enhanced security, use JWT Bearer authentication instead of client secrets. This requires uploading a certificate to the API client and using the `--jwt-cert` and `--jwt-key` flags. See [JWT Authentication](#jwt-authentication-certificate-based) for setup instructions.
85+
:::
7886

7987
::: warning
80-
The B2C CLI only supports `client_secret_basic` for the Token Endpoint Auth Method. `client_secret_post` and `private_key_jwt` aren't currently supported.
88+
For client credentials with secrets, only `client_secret_basic` is supported. `client_secret_post` isn't currently supported.
8189
:::
8290

8391
### Assigning Roles
@@ -150,12 +158,158 @@ For **User Authentication** (implicit flow), configure redirect URLs in your API
150158
| `http://localhost:8080` | Required for B2C CLI user authentication |
151159
| `https://admin.dx.commercecloud.salesforce.com/oauth2-redirect.html` | Optional - enables ODS Swagger interface with same client |
152160

153-
**Note:** Redirect URLs are not required for API clients using only Client Credentials authentication.
161+
**Note:** Redirect URLs are not required for API clients using only Client Credentials or JWT Bearer authentication.
154162

155163
::: tip Running Behind a Proxy
156164
If you're running the CLI behind a proxy where `localhost:8080` isn't reachable by the browser, set `SFCC_REDIRECT_URI` to the proxy URL (e.g., `https://proxy.example.com:8080`). The proxy should forward traffic to the CLI's local server. You can also change the local server port with `SFCC_OAUTH_LOCAL_PORT`. Make sure to add your proxy URL to the API client's redirect URLs in Account Manager.
157165
:::
158166

167+
## JWT Authentication (Certificate-Based)
168+
169+
JWT Bearer authentication (RFC 7523) provides a more secure alternative to client secrets by using public/private certificate pairs. This is ideal for production environments and CI/CD pipelines where you want to avoid storing sensitive secrets.
170+
171+
### How It Works
172+
173+
1. You generate a certificate pair (public certificate + private key)
174+
2. You register the **public certificate** in Account Manager
175+
3. The CLI uses the **private key** to sign JWT tokens for authentication
176+
4. Account Manager verifies the signature using your registered certificate
177+
178+
### Benefits
179+
180+
- **More secure**: Private key never leaves your machine
181+
- **No secrets to leak**: No client secret to store or compromise
182+
- **Better for CI/CD**: Certificates can be rotated without updating secrets across pipelines
183+
- **Industry standard**: Implements OAuth 2.0 JWT Bearer (RFC 7523)
184+
185+
### Setup Instructions
186+
187+
#### Step 1: Generate Certificate Pair
188+
189+
Generate an RSA certificate pair using OpenSSL:
190+
191+
```bash
192+
openssl req -x509 -newkey rsa:4096 \
193+
-keyout key.pem \
194+
-out cert.pem \
195+
-days 365 \
196+
-nodes \
197+
-subj "/CN=B2C CLI"
198+
```
199+
200+
This creates two files:
201+
- `cert.pem` - Public certificate (upload to Account Manager)
202+
- `key.pem` - Private key (keep secure on your machine)
203+
204+
::: tip Encrypted Keys
205+
For additional security, generate an encrypted private key by omitting `-nodes` and adding a passphrase when prompted. You'll provide the passphrase via `--jwt-passphrase` when using the CLI.
206+
:::
207+
208+
#### Step 2: Register Certificate in Account Manager
209+
210+
1. Log in to [Account Manager](https://account.demandware.com)
211+
2. Navigate to **API Client** and select your client
212+
3. Set **Token Endpoint Auth Method** to `private_key_jwt`
213+
4. Upload `cert.pem` in the **Certificates** section
214+
5. Save changes
215+
216+
::: tip Multiple Certificates per Client
217+
You can register **multiple certificates** for the same API client. This is useful for:
218+
- **Team collaboration**: Each developer generates their own key pair and registers their certificate
219+
- **Key rotation**: Add a new certificate before removing the old one (zero downtime)
220+
- **Multi-environment**: Different certificates for CI/CD, staging, production
221+
222+
Account Manager will verify JWT signatures against all registered certificates, so each team member can authenticate with their own private key while sharing the same client ID.
223+
:::
224+
225+
#### Step 3: Configure CLI
226+
227+
You can provide JWT credentials via CLI flags, environment variables, or configuration files.
228+
229+
**Using CLI flags:**
230+
231+
```bash
232+
b2c code list \
233+
--client-id your-client-id \
234+
--jwt-cert ./cert.pem \
235+
--jwt-key ./key.pem
236+
```
237+
238+
**Using environment variables:**
239+
240+
```bash
241+
export SFCC_CLIENT_ID=your-client-id
242+
export SFCC_JWT_CERT=/path/to/cert.pem
243+
export SFCC_JWT_KEY=/path/to/key.pem
244+
245+
b2c code list
246+
```
247+
248+
**Using dw.json:**
249+
250+
```json
251+
{
252+
"hostname": "your-instance.demandware.net",
253+
"client-id": "your-client-id",
254+
"jwt-cert-path": "./cert.pem",
255+
"jwt-key-path": "./key.pem"
256+
}
257+
```
258+
259+
**For encrypted keys:**
260+
261+
```bash
262+
b2c code list \
263+
--client-id your-client-id \
264+
--jwt-cert ./cert.pem \
265+
--jwt-key ./key.pem \
266+
--jwt-passphrase "your-passphrase"
267+
268+
# Or via environment variable
269+
export SFCC_JWT_PASSPHRASE=your-passphrase
270+
```
271+
272+
### Authentication Priority
273+
274+
JWT authentication is tried **after** client credentials (if client secret is available) but **before** implicit flow:
275+
276+
1. `client-credentials` - Uses client secret if available
277+
2. `jwt` - Uses JWT certificate if configured (no client secret)
278+
3. `implicit` - Opens browser for user authentication
279+
280+
To force JWT authentication even when a client secret is configured:
281+
282+
```bash
283+
b2c code list --auth-methods jwt
284+
```
285+
286+
### Troubleshooting
287+
288+
**"JWT certificate file not found"**
289+
- Verify the certificate path is correct
290+
- Use absolute paths or paths relative to current directory
291+
292+
**"Invalid JWT private key"**
293+
- Check that the key file is in PEM format
294+
- If encrypted, ensure you provide the correct passphrase via `--jwt-passphrase`
295+
296+
**"JWT authentication failed (401)"**
297+
- Verify the certificate is registered in Account Manager
298+
- Ensure the Token Endpoint Auth Method is set to `private_key_jwt`
299+
- Check that the client ID matches the API client with the registered certificate
300+
301+
**"Invalid certificate format"**
302+
- The certificate must be in PEM format (starts with `-----BEGIN CERTIFICATE-----`)
303+
- Regenerate the certificate using the OpenSSL command above
304+
305+
### Security Best Practices
306+
307+
- **Keep private keys secure**: Never commit `key.pem` to version control
308+
- **Add to .gitignore**: Include `*.pem` and `*.key` in your `.gitignore`
309+
- **Use encrypted keys in production**: Generate keys with passphrases for production use
310+
- **Rotate certificates regularly**: Generate new certificates periodically (every 90-365 days)
311+
- **Store passphrases securely**: Use secret managers for passphrases in CI/CD
312+
159313
## OCAPI Configuration
160314

161315
For operations that interact with B2C Commerce instances (code deployment, jobs, sites), you need to configure OCAPI permissions on each instance.

docs/guide/configuration.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ For detailed setup instructions including Account Manager API client creation, r
1616

1717
OAuth is required for API operations (code list/activate/delete, jobs, sites, SCAPI commands, SLAS, ODS) and can also be used for WebDAV file operations when basic auth credentials are not provided.
1818

19-
#### Client Credentials (Recommended)
19+
#### Client Credentials
2020

21-
OAuth client credentials is the recommended method for production and CI/CD use:
21+
OAuth client credentials uses a client ID and secret for non-interactive authentication:
2222

2323
```bash
2424
b2c code deploy \
@@ -27,6 +27,20 @@ b2c code deploy \
2727
--client-secret your-client-secret
2828
```
2929

30+
#### JWT Bearer
31+
32+
JWT Bearer uses certificate-based authentication for enhanced security without storing client secrets:
33+
34+
```bash
35+
b2c code deploy \
36+
--server abcd-123.dx.commercecloud.salesforce.com \
37+
--client-id your-client-id \
38+
--jwt-cert ./cert.pem \
39+
--jwt-key ./key.pem
40+
```
41+
42+
See [JWT Authentication](./authentication#jwt-authentication-certificate-based) for setup instructions.
43+
3044
#### Implicit Flow
3145

3246
For development without a client secret, use implicit flow which opens a browser for authentication:
@@ -65,6 +79,9 @@ You can configure the CLI using environment variables:
6579
| `SFCC_CODE_VERSION` | Code version for deployments |
6680
| `SFCC_CLIENT_ID` | OAuth client ID |
6781
| `SFCC_CLIENT_SECRET` | OAuth client secret |
82+
| `SFCC_JWT_CERT` | Path to JWT certificate file (cert.pem) for JWT Bearer auth |
83+
| `SFCC_JWT_KEY` | Path to JWT private key file (key.pem) for JWT Bearer auth |
84+
| `SFCC_JWT_PASSPHRASE` | Passphrase for encrypted JWT private key |
6885
| `SFCC_OAUTH_SCOPES` | OAuth scopes to request |
6986
| `SFCC_AUTH_METHODS` | Comma-separated list of allowed auth methods |
7087
| `SFCC_SHORTCODE` | SCAPI short code |
@@ -124,6 +141,18 @@ Both camelCase and kebab-case are accepted for all field names in `dw.json`. For
124141
}
125142
```
126143

144+
Or with JWT Bearer authentication:
145+
146+
```json
147+
{
148+
"hostname": "abcd-123.dx.commercecloud.salesforce.com",
149+
"code-version": "version1",
150+
"client-id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
151+
"jwt-cert-path": "./cert.pem",
152+
"jwt-key-path": "./key.pem"
153+
}
154+
```
155+
127156
### Multiple Instances
128157

129158
For projects that work with multiple instances, use the `configs` array:
@@ -219,6 +248,9 @@ For the full command reference with all flags, see [Setup Commands](/cli/setup).
219248
| `code-version` | Code version for deployments |
220249
| `client-id` | OAuth client ID |
221250
| `client-secret` | OAuth client secret |
251+
| `jwt-cert-path` | Path to JWT certificate file (cert.pem) for JWT Bearer authentication. Also accepts `jwtCertPath`. |
252+
| `jwt-key-path` | Path to JWT private key file (key.pem) for JWT Bearer authentication. Also accepts `jwtKeyPath`. |
253+
| `jwt-passphrase` | Passphrase for encrypted JWT private key. Also accepts `jwtPassphrase`. |
222254
| `username` | Basic auth username (WebDAV) |
223255
| `password` | Basic auth access key (WebDAV) |
224256
| `oauth-scopes` | OAuth scopes (array of strings) |
@@ -324,7 +356,8 @@ Plugins can add custom configuration sources like secret managers or environment
324356

325357
To prevent mixing credentials from different sources, certain fields are treated as atomic groups:
326358

327-
- **OAuth**: `clientId` and `clientSecret`
359+
- **OAuth Client Credentials**: `clientId` and `clientSecret`
360+
- **OAuth JWT Bearer**: `clientId`, `jwtCertPath`, `jwtKeyPath`, and `jwtPassphrase`
328361
- **Basic Auth**: `username` and `password`
329362

330363
If any field in a group is set by a higher-priority source, all fields in that group from lower-priority sources are ignored. This ensures credential pairs always come from the same source.
@@ -368,6 +401,7 @@ For platform-level commands (Sandbox, SLAS, and Account Manager), the CLI includ
368401
### Available Auth Methods
369402

370403
- `client-credentials` - OAuth 2.0 client credentials flow (requires client ID and secret). Used for SCAPI/OCAPI and WebDAV.
404+
- `jwt` - OAuth 2.0 JWT Bearer flow (requires client ID, certificate, and private key). Used for SCAPI/OCAPI and WebDAV. More secure than client credentials.
371405
- `implicit` - OAuth 2.0 implicit flow (requires client ID only, opens browser for login). Used for SCAPI/OCAPI and WebDAV.
372406
- `basic` - Basic authentication with username and access key. Used for WebDAV operations only.
373407
- `api-key` - API key authentication. Used for MRT commands only.

packages/b2c-tooling-sdk/src/auth/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*
1414
* - {@link BasicAuthStrategy} - Username/password authentication for WebDAV operations
1515
* - {@link OAuthStrategy} - OAuth 2.0 client credentials for OCAPI and platform APIs
16+
* - {@link JwtOAuthStrategy} - OAuth 2.0 JWT Bearer (certificate-based authentication)
1617
* - {@link ImplicitOAuthStrategy} - Interactive browser-based OAuth for CLI/desktop apps
1718
* - {@link ApiKeyStrategy} - API key authentication for MRT services
1819
*
@@ -80,6 +81,8 @@ export {OAuthStrategy, decodeJWT} from './oauth.js';
8081
export type {OAuthConfig} from './oauth.js';
8182
export {ImplicitOAuthStrategy} from './oauth-implicit.js';
8283
export type {ImplicitOAuthConfig} from './oauth-implicit.js';
84+
export {JwtOAuthStrategy} from './oauth-jwt.js';
85+
export type {JwtOAuthConfig} from './oauth-jwt.js';
8386
export {ApiKeyStrategy} from './api-key.js';
8487
export {StatefulOAuthStrategy} from './stateful-oauth-strategy.js';
8588
export type {StatefulOAuthStrategyOptions} from './stateful-oauth-strategy.js';

0 commit comments

Comments
 (0)