Introduction
Setting Up OAuth 2.0 JWT Bearer Token Flow for Test Automation
This guide outlines the steps to configure the Salesforce OAuth 2.0 JWT Bearer Token Flow, specifically tailored for test automation scenarios. This flow enables your automated tests or scripts to authenticate with Salesforce without requiring interactive browser logins, bypassing common challenges like MFA and geolocation verification. This is the best way. Alternative is using TOTP, but still there is then a slight change you will be blocked by geolocation.
Phase 1: Generate Cryptographic Keys (Local Machine)
This phase involves creating a private key and a public certificate that Salesforce will use to verify the authenticity of your automation requests.
Prerequisites:
- OpenSSL: Available on most Linux/macOS. For Windows, download a pre-compiled binary.
Steps:
-
Open your terminal or command prompt.
-
Navigate to a secure directory where you will store your key files.
-
Generate an RSA Private Key (Encrypted):
- This creates an encrypted private key. You will be prompted to enter and verify a passphrase. Remember this passphrase.
openssl genrsa -des3 -passout pass:your_strong_passphrase -out server.pass.key 2048
-
Decrypt the Private Key:
- This removes the passphrase, making the key usable by your automation without manual passphrase entry. The
server.key
file is highly sensitive and must be kept absolutely secure.
openssl rsa -passin pass:your_strong_passphrase -in server.pass.key -out server.key
- This removes the passphrase, making the key usable by your automation without manual passphrase entry. The
-
Generate an X.509 Certificate (Public Key):
- This command creates a public certificate (
server.crt
) from your private key. This certificate will be uploaded to Salesforce. You will be prompted for various details (e.g., Country, Organization, Common Name). Provide reasonable values.
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 365
- You now have
server.key
(private key) andserver.crt
(public certificate).
- This command creates a public certificate (
Phase 2: Configure Connected App in Salesforce
This phase involves setting up a “Connected App” within your Salesforce org to recognize and trust your automation.
Prerequisites:
- Access to your Salesforce Developer Org or Sandbox with “Customize Application” and “Manage Connected Apps” permissions.
- The
server.crt
(public certificate) file generated in Phase 1.
Steps:
-
Log in to your Salesforce Org (e.g.,
https://login.salesforce.com
for production/developer,https://test.salesforce.com
for sandboxes). -
Navigate to Setup: Click the gear icon (⚙️) and select “Setup.”
-
Go to App Manager: In the Quick Find box, search for
App Manager
and select it under “Apps.” -
Create a New Connected App: Click the New Connected App button.
-
Fill in Basic Information:
- Connected App Name: E.g.,
Test Automation JWT App
(or similar descriptive name). - API Name: (Auto-populates or define your own).
- Contact Email: Your email address.
- Connected App Name: E.g.,
-
Configure API (Enable OAuth Settings):
- Check Enable OAuth Settings.
- Callback URL: Enter a placeholder like
http://localhost/
orhttps://your-automation-server.com/oauth/callback
. This is required but not directly used by the JWT flow. - Check Use digital signatures.
- Click Choose File and upload your
server.crt
file. - Selected OAuth Scopes: Crucial for defining permissions. Select the necessary scopes for your automation. Common ones include:
Access and manage your data (api)
Perform requests on your behalf at any time (refresh_token, offline_access)
(useful for long-running processes)- Add any other specific scopes your tests might require (e.g.,
full
for broad access, or more granular scopes).
-
Save the Connected App: Click Save.
- Copy the
Consumer Key
displayed on the next page. This is yourclient_id
for the JWT flow. The Consumer Secret is generally not used for the JWT Bearer Token flow.
- Copy the
-
Manage Connected App Policies (Pre-Authorization):
- From the Connected App detail page, click the Manage button. (If you navigated away, go back to App Manager, find your app, and click Manage from its dropdown).
- On the management page for your Connected App, click Edit Policies.
- Under the OAuth Policies section, find Permitted Users. Change this setting from “All users may self-authorize” to “Admin approved users are pre-authorized”. This step is vital for server-to-server communication as it allows your app to authenticate without requiring interactive user consent each time.
- Click Save.
-
Assign Profiles/Permission Sets:
- Still on the Connected App’s “Manage” page (after editing policies), scroll down to the Profiles or Permission Sets related lists.
- Click Manage Profiles or Manage Permission Sets.
- Select the appropriate profile(s) or create/assign a specific permission set for the Salesforce integration user that your automation will use. This user must be explicitly granted access to use this Connected App. For example, you might assign the “System Administrator” profile for broad access in a dev environment, or a more restricted custom profile/permission set for production-like testing.
- Click Save.
Phase 3: Implement JWT Assertion and Token Request (Your Test Automation Code)
This phase involves writing code within your test automation framework (e.g., Node.js with Playwright) to generate a JWT assertion, sign it with your private key, and then exchange it for a Salesforce access token.
Prerequisites:
- Node.js and npm (or your chosen language’s package manager) installed.
- The
server.key
(private key) file from Phase 1. - The
Consumer Key
(Client ID) obtained from Salesforce in Phase 2. - The username of the Salesforce integration user (e.g.,
automation.user@yourorg.com
). This user must be active and assigned to a profile/permission set that is linked to your Connected App.
Steps:
-
Install Necessary Libraries (Node.js Example):
- If using Node.js, you’ll typically need libraries for JWT handling and making HTTP requests.
npm install jsonwebtoken axios
-
Create an Authentication Utility File (e.g.,
salesforceAuth.js
):- This script will encapsulate the logic for generating the JWT and fetching the access token. Ensure your
server.key
file is accessible to this script (e.g., in the same directory or a secure sub-directory).
YOUR_CONNECTED_APP_CONSUMER_KEY
import jwt from 'jsonwebtoken'; import axios from 'axios'; import fs from 'fs'; import path from 'path'; export async function getSalesforceAccessToken() { // --- Configuration (UPDATE THESE VALUES) --- const CONSUMER_KEY = '3MVG98Gq2O8Po4ZmpGm16AXnMtSOfFlR4hU4gkJWJH8HTtuVVW3skuFYaJpyizxFWVFoxnzy.eIi19qs4pEsi'; // Replace with your Consumer Key const SALESFORCE_USERNAME = 'ralphvanderhorst@learnautomatedtesting.com'; // Replace with your Salesforce integration user's username const PRIVATE_KEY_PATH = path.resolve(path.dirname(new URL(import.meta.url).pathname), 'server.key'); // Adjust path to your server.key const AUTH_URL = 'https://login.salesforce.com/services/oauth2/token'; // Use https://test.salesforce.com for sandboxes! // --- Read Private Key --- let privateKey; try { privateKey = fs.readFileSync(PRIVATE_KEY_PATH, 'utf8'); } catch (error) { console.error(`Error reading private key file at ${PRIVATE_KEY_PATH}:`, error.message); throw new Error('Failed to read private key. Ensure it exists and path is correct.'); } // --- Create JWT Payload --- const issuedAt = Math.floor(Date.now() / 1000); // Current timestamp in seconds const expiresAt = issuedAt + (5 * 60); // Token expires in 5 minutes (300 seconds) const jwtPayload = { iss: CONSUMER_KEY, sub: SALESFORCE_USERNAME, aud: 'https://login.salesforce.com', // Base URL for production exp: expiresAt }; // --- Sign JWT --- let assertion; try { assertion = jwt.sign(jwtPayload, privateKey, { algorithm: 'RS256' }); } catch (error) { console.error('Error signing JWT:', error.message); throw new Error('Failed to sign JWT assertion. Check private key format or passphrase (if any).'); } // --- Request Access Token from Salesforce --- try { console.log('Requesting Salesforce Access Token...'); const response = await axios.post(AUTH_URL, new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion }).toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log('Successfully obtained Salesforce Access Token.'); return response.data; // Contains access_token, instance_url, etc. } catch (error) { console.error('Error requesting access token from Salesforce:', error.response ? error.response.data : error.message); throw new Error('Failed to get Salesforce access token.'); }
- This script will encapsulate the logic for generating the JWT and fetching the access token. Ensure your
}
```
- Integrate into Playwright Tests:
- Use Playwright’s
test.beforeAll
hook to obtain the access token once before your tests run. - Use Playwright’s
request
context to make authenticated API calls with the obtained token.
// myPlaywrightTest.spec.js const { test, expect } = require('@playwright/test'); const getSalesforceAccessToken = require('./salesforceAuth'); // Adjust path as needed let salesforceApiCredentials; // To store { access_token, instance_url } test.beforeAll(async () => { try { salesforceApiCredentials = await getSalesforceAccessToken(); if (!salesforceApiCredentials || !salesforceApiCredentials.access_token || !salesforceApiCredentials.instance_url) { throw new Error('Did not receive valid Salesforce credentials for API tests.'); } console.log('Salesforce API credentials obtained for Playwright tests.'); } catch (error) { console.error('Failed to authenticate with Salesforce before tests:', error.message); process.exit(1); // Exit if authentication fails, as tests won't run without it } }); test('should fetch a Salesforce record via API for test data verification', async ({ request }) => { // Replace with an actual record ID from your Salesforce org for testing const recordId = '001XXXXXXXXXXXXXXXX'; // Example: an Account ID const response = await request.get( `${salesforceApiCredentials.instance_url}/services/data/v58.0/sobjects/Account/${recordId}`, { headers: { 'Authorization': `Bearer ${salesforceApiCredentials.access_token}` } } ); expect(response.ok()).toBeTruthy(); // Check if the API call was successful (2xx status) const data = await response.json(); console.log('Fetched record for verification:', data); expect(data.Id).toBe(recordId); expect(data.Name).toBeDefined(); }); test('should create test data (e.g., a new Contact) via API', async ({ request }) => { const newContactLastName = `AutoContact ${Date.now()}`; const response = await request.post( `${salesforceApiCredentials.instance_url}/services/data/v58.0/sobjects/Contact/`, { headers: { 'Authorization': `Bearer ${salesforceApiCredentials.access_token}`, 'Content-Type': 'application/json' }, data: { FirstName: 'Playwright', LastName: newContactLastName, Company: 'Automated Inc.' } } ); expect(response.ok()).toBeTruthy(); const responseBody = await response.json(); console.log('Created Contact Response:', responseBody); expect(responseBody.success).toBe(true); expect(responseBody.id).toBeDefined(); // You can store responseBody.id to clean up this record later if needed });
- Use Playwright’s
Key Considerations for Test Automation:
- Security of Private Key: The
server.key
is paramount. Never commit it to public repositories. Store it securely and use environment variables or a secure vault for its path. - Dedicated Integration User: Use a dedicated Salesforce user for your automation. This user should have a profile/permission set with only the necessary API permissions to perform your test operations. This adheres to the principle of least privilege.
- Token Expiration: The
access_token
expires (typically after 2 hours). YourgetSalesforceAccessToken
function handles this by requesting a new token each time it’s called. For long-running test suites, ensure this function is called as needed or that your test runner handlesbeforeAll
appropriately. - Error Handling: Implement robust error handling in your authentication utility and API calls to quickly diagnose issues.
- Test Data Management: This JWT flow is ideal for setting up and tearing down test data via API, which is much faster and more reliable than doing it through the UI.
By following these steps, your Playwright tests can securely and efficiently interact with the Salesforce API, bypassing interactive login challenges and streamlining your automation workflow.