Overview
Salesforce provides several methods to Authenticate against it’s various API’s. A common method used for Server to Server integration is the OAuth 2.0 JWT Bearer Flow. This article provides detailed steps to build this flow in Python3. Of note, while this article provides complete details on how to build and execute this Authentication Flow, I don’t get into actually using an API beyond a simple test to ensure our token and connection work.
Prerequisites
- Access to a Salesforce Sandbox or Dev Org
- The latest version of Python3 installed locally
- Visual Studio Code installed
Step By Step
Create an x509 Certificate and Private Key
See Reference # 1 for more detailed instructions if needed
-
Open a terminal
-
Create a project directory - mkdir jwt
-
Change to the new directory - cd jwt
-
Generate a private key, and store it in a file called server.key
Change SomePassword to a unique password
openssl genrsa -des3 -passout pass:SomePassword -out server.pass.key 2048
-
Export the key file to a file named server.key
openssl rsa -passin pass:SomePassword -in server.pass.key -out server.key
-
You will now have 2 files created, server.pass.key & server.key. You can delete server.pass.key as it’s no longer needed
-
Generate a certificate signing request using the server.key file. You will be prompted for attributes which you should complete, although the actual values matter little.
openssl req -new -key server.key -out server.csr
-
Generate a self-signed digital certificate from the server.key and server.csr files
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
-
At this point you should have 3 files:
- server.key - The private key. This is the file you will use in your app to sign your Token Request
- server.crt - The digital certification. You upload this file when you create the connected app required by the JWT bearer flow.
- server.csr - The certificate signing request, which you will not use.
Create a Service Account (User) in Salesforce
- Create a Profile (we will use the Salesforce Integration User License)
- Clone the Salesforce API Only System Integrations
- Name = Service API User
- Create a Permission Set
- Name = Service API Permission Set
- License = Salesforce API Integration
- Edit the Object Settings and grant Read Access to Accounts
- Under Field Permissions, check Read Access for all fields
- Save the changes
- Create a User
- Setup\Users
- New User
- First Name = API
- Last Name = User
- Email = your email
- Username = Something unique, like api.user@test.not
- User License = Salesforce Integration
- Profile = Service API User
- Active = checked
- Time Zone = your time zone
- Uncheck “Generate new password…”
- Click Save
- Assign the Service API Permission Set to the User
Create a Connected App in Salesforce
- Setup\App Manager
- New Connected App
- Connected App Name = API Test
- API Name = API_Test
- Contact Email = your email
- Enable OAuth Settings = checked
- Callback URL = https://localhost
- Use digital signatures
- Choose File
- Select server.crt from earlier step
- Available OAuth Scopes
- Select - Manage user data via APIs (api)
- Select - Perform requests at any time (refresh_token, offline_access)
- Click Save
Grant the user access to the connected app
- View the connected app, click Manage then Edit Policies
- Change Permitted Users from All users may self-authorize to Admin approved users are pre-authorized and click Save
- Scroll down to Profiles and click Manage Profiles
- Add the Profile assigned to your API User (Service API User)
Create a new Directory and Populate with Core Files
- Create a new Directory (wherever you choose) named sf-jwt-app
- Open Visual Studio Code
- Open the Folder sf-jwt-app (File\Open Folder)
- Create a file for our app named app.py (empty for now)
- Create a new directory for our certificate named certs
- Copy the server.key file generated earlier into the certs directory
Create a Virtual Environment
- In Visual Studio Code, open the Command Pallet (Command Shift P)
- Choose Python: Create Environment
- Choose Venv
- Choose the latest version of Python3 installed
- Open a new Terminal and verify you are in the virtual environment
Create a File and Populate our Environment Variables
-
Create a new File in the root
- name = .env
- Open the file in the editor
- Paste in this text
KeyFile = 'certs/server.key' ClientId = '' UserName = '' Domain = ''
-
Update the .env file with your attributes
- ClientId
- In Salesforce, view your API Test connected app
- Click Manage Consumer Details
- Copy the Consumer Key and paste after ClientId in the file (between the single quotes)
- UserName
- Copy the Salesforce username you created earlier and paste after UserName in the file (between the single quotes)
- Domain
- https://test.salesforce.com //for a Sandbox
- https://login.salesforce.com //for a Production or Developer org
- ClientId
Install Dependencies
- Open a new Terminal inside Visual Studio Code
- Type/Paste this text
pip3 install python-dotenv, pyjwt, requests, cryptography
- Press Enter
Create our Python Files
-
Create a new file in the root to hold our Authorization code
- Name = auth.py
- Open the file in the editor
- Paste in this code
# Imports from dotenv import load_dotenv # pip3 install python-dotenv from datetime import datetime import jwt # pip3 install pyjwt import time import requests # pip3 install requests import json import os def doAuth(): # Get environment variables from .env file load_dotenv() KEY_FILE = os.getenv('KeyFile') ISSUER = os.getenv('ClientId') SUBJECT = os.getenv('UserName') DOMAIN = os.getenv('Domain') # Load the Private Key from the .key File print('Loading Private Key...') with open(KEY_FILE) as kf: private_key = kf.read() # Create the Claim print('Creating Claim...') claim = { 'iss': ISSUER, 'sub': SUBJECT, 'aud': '{}'.format(DOMAIN), 'exp': int(time.time()) + 300 } # Create the Assertion print('Generating Signed JWT Assertion...') assertion = jwt.encode(claim, private_key, algorithm='RS256', headers={'alg':'RS256'}) # Request an Authorization Token print('Making OAuth request...') endpoint = '{}/services/oauth2/token'.format(DOMAIN) r = requests.post(endpoint, data = { 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion': assertion }) # Print the Response print('Response...') print('Status', r.status_code) print(r.json()) # Parse the Response and Store the Auth Information reply = json.loads(r.text) global access_token global instance_url access_token = reply['access_token'] instance_url = reply['instance_url']
-
Add our Test code
- Open app.py
- Open the file in the editor
- Paste in this code
- Change the Account Id to an Account in your org. Make sure the record has an Account Number.
# Imports import auth import requests auth.doAuth() url = auth.instance_url + '/services/data/v58.0/sobjects/Account/001Dn00000Gi7HJIAZ?fields=Name,AccountNumber' r = requests.get(url, headers = {"Authorization":"Bearer " + auth.access_token}) print(r.json()) AccountId = r.json()['Id'] AccountNum = r.json()['AccountNumber'] AccountName = r.json()['Name'] print('Account Id = ' + AccountId) print('Account Number = ' + AccountNum) print('Account Name = ' + AccountName)
Test
- Right click on app.py and click Run Python File in Terminal
- If everything worked correctly, you should see something that looks like this in the open Terminal window:
Next Steps
- To use this against one of the Salesforce API’s
- Modify your Permission Set to include the correct permissions for your Service user
- Modify app.py and substitute the test code for your real code