Salesforce JWT flow: A Guide to Secure Server Auth

Why use the Salesforce JWT flow for integrations?

If you have ever had to set up an automated integration where nobody is around to click a “Login” button, you have probably run into the Salesforce JWT flow. It is the go-to method for server-to-server authentication when you need things to just work in the background without a human in the loop. I have seen teams try to use the old Username-Password flow for this, but honestly, that is a security headache you do not want. The JWT (JSON Web Token) approach is much cleaner and more secure.

In my experience, this flow is perfect for CI/CD pipelines, backend microservices, or any middleware that needs to talk to your org. It uses a digital signature, so you do not have to worry about passwords expiring or being stored in plain text. But here is the thing: you need to get the certificate setup exactly right, or Salesforce will just give you a generic error that explains nothing. Let’s break down how to actually get this running.

When you are looking at different integration options for Salesforce, the JWT flow stands out because it issues short-lived tokens. You sign a request with your private key, Salesforce verifies it with the public cert you uploaded, and you get an access token. Simple, right?

How to set up the Salesforce JWT flow

First, you need a key pair. I usually just use OpenSSL for this. You will generate a private key and a self-signed certificate. You keep the private key very safe on your server, and you upload the certificate to a Connected App in Salesforce. When your app wants to log in, it creates a JWT, signs it with that private key, and sends it to the token endpoint.

  1. Generate your RSA keys (private key and public cert).
  2. Create a Connected App and check the box for “Use Digital Signature.”
  3. Upload your server.crt file.
  4. Set your OAuth scopes. Usually, api or full is what people go for.
  5. Note down the Consumer Key – you will need this as the “issuer” (iss) in your code.

One thing that trips people up is the “Permitted Users” setting in the Connected App. If you leave it as “All users may self-authorize,” the flow might fail unless the user has already manually logged in once. I always recommend changing this to “Admin approved users are pre-authorized” and then adding the specific Profile or Permission Set to the app. It saves a lot of debugging time later.

A realistic mockup of the Salesforce Connected App settings page focusing on the OAuth Policies configuration.
A realistic mockup of the Salesforce Connected App settings page focusing on the OAuth Policies configuration.

Building the JWT Assertion

The JWT itself is just a JSON object with a few specific fields. You need the iss (your Consumer Key), the sub (the Salesforce username), the aud (either login.salesforce.com or test.salesforce.com), and an exp (expiration time). The expiration must be short. If you set it for an hour from now, Salesforce will reject it. Keep it to about 3 to 5 minutes.

So what does the request look like? You will POST to the token endpoint with a grant_type of urn:ietf:params:oauth:grant-type:jwt-bearer and your signed string as the assertion. If you are used to the differences between SOAP and REST, you will find this follows standard REST patterns quite nicely.

Pro Tip: Always double-check your server’s clock. If your server time drifts even by a minute, the exp timestamp in your Salesforce JWT flow request might already be “expired” by the time it hits Salesforce’s servers, leading to a frustrating invalid_grant error.

Troubleshooting the Salesforce JWT flow

We have all been there. You run your script and get {"error":"invalid_grant","error_description":"expired authorization code"}. It is almost never an expired code; it is usually a mismatch in your configuration. Here are the usual suspects I check first.

  • Username Mismatch: Is the sub field exactly the same as the username in the org? Even a tiny typo will break it.
  • Audience: If you are hitting a sandbox, the aud must be https://test.salesforce.com. If it is production or a developer edition, use https://login.salesforce.com.
  • Certificate: Did you upload the right .crt file? If you regenerated your keys and forgot to update the Connected App, it won’t work.
  • Permissions: Does the user actually have the right to use the Connected App? Check the Profile or Permission Set assignments.

Key Takeaways

  • The Salesforce JWT flow is built for automated, server-to-server integrations.
  • It requires an RSA key pair and a properly configured Connected App.
  • The “Admin approved users” setting is your friend for avoiding “user not authorized” errors.
  • Keep the JWT expiration short to satisfy Salesforce’s security requirements.
  • The private key must stay private – never check it into version control.

Now, while setting this up can feel like a lot of steps, once it is running, it is incredibly stable. You do not have to worry about refresh tokens or interactive logins. Just sign, post, and get your work done. If you are moving away from legacy integrations, making the switch to the Salesforce JWT flow is one of the best moves you can make for your org’s security posture.

The short answer? If you are building a backend service that needs to talk to Salesforce, this is the way to go. Just keep your keys safe and your timestamps accurate, and you will be fine.