---
schema_version: "1"
id: 4060b0246cbd0740e3ebc8d99fb6a1917b698c6a533eaa71358b4e53263c9659
title: Configure a JWT Bearer Flow
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/analytics/sdk/guides/sdk-setup-auth-extended.md
canonical_url: https://developer.salesforce.com/docs/analytics/sdk/guide/sdk-setup-auth-extended.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 54df4190c7c165464c8e25d9c4a88bef79fc3323b25ae6ff5a5ed306ec73859b
products:
  - Tableau Next Embedding SDK
  - analytics
---
# Configure a JWT Bearer Flow

The standard External Client App (ECA) setup for Tableau Next embedding uses the OAuth 2.0 web server flow. If your app requires non-interactive, server-to-server authentication, you can configure a JWT bearer flow by using the Salesforce External Client App Manager. The web server flow remains the recommended flow for SDK embedding on behalf of interactive users. The JWT bearer flow is an alternative for non-interactive scenarios only.

| **User Permissions Needed**                         |                                               |
| --------------------------------------------------- | --------------------------------------------- |
| To view Salesforce Setup:                           | View Setup and Configuration                  |
| To manage External Client Apps in Salesforce Setup: | Create, edit, and delete External Client Apps |
| To assign permission sets:                          | Assign Permission Sets                        |

## JWT Bearer Flow Overview

For Tableau Next Embedding, the web server flow is the recommended and default flow.

The JWT bearer flow is a non-interactive, server-to-server OAuth 2.0 flow. Your app proves its identity by signing a JSON Web Token (JWT) with an RSA private key and sending it to the Salesforce token endpoint. Salesforce verifies the signature against a public certificate uploaded to the ECA and returns an access token directly. There’s no browser redirect and no user login.

To use the JWT bearer flow with embedding, you're required to perform extra configuration steps. You must create a public certificate, upload it to your ECA, and then create and assign a custom permission set.

## Generate an RSA Key Pair and X.509 Certificate

The JWT bearer flow signs each token request with your app's private key. Generate a 2048-bit RSA private key and a self-signed X.509 public certificate. The private key stays with your app, and you upload the public certificate to your ECA.

```bash
# Generate the private key
openssl genrsa -out sf_private_key.pem 2048

# Generate the public X.509 certificate (valid for 365 days)
openssl req -new -x509 -key sf_private_key.pem -out sf_public_cert.crt -days 365
```

The `openssl req -new -x509` command is interactive and prompts for a subject. To skip prompts, add `-subj` to your command, similar to `-subj "/CN=tableau-next-eca"`.

## Update Your External Client App in Salesforce Setup

This flow assumes you've created your ECA in Tableau Next and are now editing it for more options.

1. From the Salesforce setup page, enter _External_ in the Quick Find box, and then select **External Client App Manager**.
2. For your existing ECA, in the dropdown menu, select **Edit Settings**.
   ![Edit the settings for your existing External Client App.](https://developer.salesforce.com/docs/media/sdk/sdk_eca_edit_settings.png '{"class": "image-lg"}')

3. Open the OAuth Setting section.

   ![Edit the OAuth settings for an existing external client app.](https://developer.salesforce.com/docs/media/sdk/sdk_eca_edit_oauth_settings.png '{"class": "image-lg"}')

4. For **OAuth Scopes**, select the scopes you need for your flow.
   - We recommend these OAuth scopes when using a **JWT Bearer Flow** for embedding, `Manage user data via Web browsers (web)` and `Perform requests at any time (refresh_token, offline_access)`.
5. In the Flow Enablement section, select the **JWT Bearer Flow**.
   - Upload the public certificate file, `sf_public_cert.crt`.
6. For Security, select **Require secret for Refresh Token** and any other options you need. Configure these options only if your security policy requires them.
7. Save your changes.

Before leaving this page, on the Settings tab, click **Consumer Key and Secret** to copy and save the values. You need the consumer key value for the `client_id` and the consumer secret value for the `client_secret`.

## Create a Custom Permission Set

The JWT bearer flow requires a pre-authorized JWT subject user, the user named in the JWT's sub claim, in the ECA settings. Pre-authorization is granted by adding a custom permission set to the allowlist in the ECA. Create a custom permission set with the access your JWT subject user needs. Creating a permission set scoped to the access for your JWT subject user only follows the principle of least privilege.

1. In Salesforce Setup, enter _Permission Sets_ in the Quick Find box, and then select **Permission Sets**.
2. Click **New**.
3. Enter a label and API name. For example, set the label to _Tableau Next Embedding JWT_ and the API name to _TableauNextEmbeddingJWT_.
4. Leave **License** set to **None** unless your org requires a specific user license for this permission set.
5. Save the customized permission set.

On the customized permission set details page, configure the access required for Tableau Next embedding. At a minimum, the permission set needs:

- System Permissions: grant any system-level permissions that your org requires for Tableau Next API access
- Object Settings: grant Read on the Tableau Next objects that your app needs read access to
- Custom Permissions: assign any Tableau Next custom permissions required by your app

## Assign the Custom Permission Set to the JWT Subject User

1. On the custom permission Details page, click **Manage Assignments**.
2. Click **Add Assignment**.
3. Select the JWT subject user, the user named in the sub claim of your JWT, or a profile whose users will act as JWT subjects.
4. Click **Assign** and then **Done**.

## Add the Custom Permission Set to the ECA Allowlist

1. In Salesforce Setup, enter _External_ in the Quick Find box, and then select **External Client App Manager**.
2. To edit your ECA, select it from the list of apps.
3. Click **Edit**.
4. On the OAuth Policies panel, update the _Permitted Users_ value to **Admin approved users are pre-authorized**.
   ![Editing the OAuth Policies plug-in policy.](https://developer.salesforce.com/docs/media/sdk/setup_admin_approved_plugin.png '{"class": "image-lg"}')

5. On the App Policies panel, add the custom permission set to the **Select Permission Sets** list.
   ![Add the custom permission set to the App Policies.](https://developer.salesforce.com/docs/media/sdk/setup_eca_app_policies.png '{"class": "image-lg"}')

6. Save and close.

For your users that are viewing embedded Tableau Next assets in your web app, assign the custom permission set. This assignment adds users for pre-authorization. For more information on assigning permission sets to users, see [Assign Tableau Next User Permissions](https://developer.salesforce.com/docs/analytics/sdk/guide/sdk-setup-users.md).

## Securely Store the Private Key

On the Settings tab for your ECA, click **Consumer Key and Secret** and copy the consumer key. The consumer key is used as the `iss` claim in the JWT. Store the private key securely on your app server.

For Node.js, the recommended approach is to use an environment variable, with the key file kept out of source control:

```bash
# .env (do not commit)
SF_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
<your private key here>
-----END PRIVATE KEY-----"
```

For Heroku, use a config var:

```bash
heroku config:set SF_PRIVATE_KEY="$(cat sf_private_key.pem)"
```

## Exchange the JWT for an Access Token

Your app builds a signed JWT and issues a POST to the Salesforce token endpoint with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`. Salesforce verifies the signature against the uploaded certificate and returns an access token.

```javascript
import express from "express";
import jwt from "jsonwebtoken";
import crypto from "crypto";
import dotenv from "dotenv";

dotenv.config();
const app = express();

app.post("/jwtBearerToken", async (req, res) => {
  try {
    const privateKey = process.env.SF_PRIVATE_KEY;
    if (!privateKey) {
      return res.status(500).json({ error: "Missing Salesforce private key" });
    }

    // This must be the Salesforce login URL — login.salesforce.com,
    // test.salesforce.com, or your My Domain login URL. NOT the
    // Lightning URL or the Setup URL.
    const LOGIN_URL = "https://login.salesforce.com";

    const now = Math.floor(Date.now() / 1000);
    const jwtClaimSet = {
      iss: "<consumer_key_from_your_External_Client_App>",
      sub: "<salesforce_username>", // full Salesforce username of the pre-authorized JWT subject user
      aud: LOGIN_URL, // Salesforce login URL
      exp: now + 3 * 60, // 3 minutes from now
      nbf: now, // not-before — recommended per RFC 7523
      jti: crypto.randomUUID(), // unique JWT id — replay defense
    };

    const assertion = jwt.sign(jwtClaimSet, privateKey, { algorithm: "RS256" });

    const params = new URLSearchParams({
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion,
    });

    const tokenResponse = await fetch(`${LOGIN_URL}/services/oauth2/token`, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: params.toString(),
    });

    if (!tokenResponse.ok) {
      return res.status(500).json({
        error: "Failed to fetch token",
        details: await tokenResponse.text(),
      });
    }

    const tokenData = await tokenResponse.json();
    return res.json(tokenData); // { access_token, instance_url, id, token_type, issued_at }
  } catch (err) {
    return res.status(500).json({ error: "Internal server error" });
  }
});
```

**note:** This example is JavaScript and runs on Node.js with Express. You can implement the same JWT bearer flow in any server-side language, substituting the equivalent JWT signing and HTTP primitives in your stack of choice. For example, Java with Spring Boot using `java.security` and a JWT library, Python with `pyjwt` and requests, Go with `golang-jwt/jwt`, Ruby on Rails with `ruby-jwt`, or .NET / C# with `System.IdentityModel.Tokens.Jwt`. The endpoint path, claim set, and request and response shapes are identical regardless of language.

## Generate the Frontdoor URL and Embed

When your app has the access token, generate the Frontdoor URL with a POST to `${instance_url}/services/oauth2/singleaccess` and pass the resulting Frontdoor URL to the SDK as the `authCredential` value. This step is identical to the web server flow. See [Generate the Access Token and Frontdoor URL for Tableau Next Embedding](/docs/analytics/sdk/guide/sdk-access-token.html).
