Use VDS with an Embedded Tableau Viz


🕐 8 min read

If you are building a web application that both embeds a Tableau viz and calls VizQL Data Service (VDS) from the same browser page, you need to configure your app so that the browser can attach the Tableau session cookie to both the embedded viz requests and the VDS API calls. This topic explains why that is challenging, and how to solve it using a reverse proxy, or a wildcard cookie domain (on Tableau Server only).

When a user authenticates with Tableau—either by loading an embedded viz or by signing in directly—Tableau sets an HttpOnly session cookie in the browser (that is, the workgroup_session_id on Tableau Server and Tableau Cloud). This cookie:

This means that if your web application lives on a different domain than Tableau, VDS calls from the browser will not include the session cookie, and authentication will fail.

The Domain and Path attributes in the Set-Cookie header control which URLs receive a cookie. To get the embedded viz and VDS calls to share the Tableau session cookie, you configure your setup so that both run under the same cookie scope. The right approach depends upon your deployment.

The most reliable solution for Tableau Cloud and Tableau Server deployments is to set up a reverse proxy. This approach is recommended where the web app and Tableau are on different domains. For more information about this setup and the requirements, see How the reverse proxy works.

If you have a deployment where Tableau Server and the web app are on different hosts, but are in the same domain, you have another option. You can set the session cookie on a wildcard domain, which lets you avoid setting up a reverse proxy.


Set up reverse proxy for Tableau Cloud

Setting up a reverse proxy is the most reliable way to enable the embedded viz and the VDS calls to share the same Tableau session cookie. This section shows an example setup that uses a Node.js backend deployed to Heroku, with the Nginx buildpack as a reverse proxy. See How the reverse proxy works for more information about reverse proxies.

Prerequisites

Step 1 — Assign API Access on your data source

  1. In Tableau Cloud, open the data source you want to query.
  2. Open the Permissions dialog and assign the API Access capability to the relevant users or groups.

For more information, see Assign API access capability.

Step 2 — Create a Connected App and generate a JWT

  1. In Tableau Cloud, go to Settings > Connected Apps and create a new Connected App.
  2. Note the Client ID, Secret ID, and Secret Value.
  3. On your Node.js server, generate a JWT with the following claims:
    • kid: your Secret ID
    • iss: your Client ID
    • sub: the Tableau user’s email
    • aud: tableau
    • scp: ["tableau:views:embed", "tableau:viz_data_service:read"]
    • exp: short expiry (5 minutes recommended)
  4. Sign the JWT with your secret using HS256.

For more information on using a JWT with VDS, see Configure authentication.

Step 3 — Find the session ID and the workbook data source ID

For instructions, see Find the workbook session ID and workbook data source ID.

Step 4 — Build the Node.js backend

The Node.js server needs to handle two things:

  1. Serve index.html with the embedded viz and a button to trigger the VDS query.
  2. Expose a VDS proxy endpoint (for example, POST /node/query) that:
    • Receives the Tableau session cookie forwarded by Nginx from the browser.
    • Calls the VDS Query data source method on Tableau Cloud along with the session headers, X-Session-Id and Global-Session-Header.
    • Returns the VDS response to the browser.

A minimal VDS request body looks like this:

{
  "datasource": {
    "workbookDatasourceId": "1a2a3b4b-5c6c-7d8d-9e0e-1f2f3a4a5b6b"
  },
  "query": {
    "fields": [
      { "fieldCaption": "State/Province" }
    ]
  }
}

For information about building queries, see Query a Data Source.

Step 5 — Configure Nginx as a reverse proxy

Create your Nginx configuration file (for example, config/nginx.conf.erb) with the following three routing rules:

# Serve index.html and other static assets from Node.js
location = / {
    proxy_pass http://localhost:;
}

# Route VDS proxy calls to Node.js backend
location /node/ {
    proxy_pass http://localhost:;
}

# Proxy all other requests (viz, auth) to Tableau Cloud
location / {
    proxy_pass https://;
    proxy_set_header Host ;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

This ensures that the viz is loaded through your app domain, so the Tableau session cookie is scoped to your app domain. When the browser then calls /node/query, the same cookie is automatically attached.

Step 6 — Build the frontend

In the web app, in this example, index.html, embed the viz using the Tableau Embedding API v3, pointing src at your app domain and the viz path (Nginx proxies it to Tableau Cloud). This web app also has a button that sends a VDS query to Tableau when clicked.

<tableau-viz
  src="https://views/YourWorkbook/YourView"
  token="">
</tableau-viz>

<!--  add a button to call VDS --->
<div id="vizQL">
  <h2>VizQL Data Service</h2>
  <button type="button" id="query-btn" disabled="true">Call VizQL Data Service</button>
  <pre id="results"></pre>
</div>

Add a button that calls your VDS proxy endpoint and displays the result. Include the X-Session-Id and Global-Session-Header in the call. For instructions on finding those values, see Find the workbook session ID and workbook data source ID

document.getElementById('query-btn').addEventListener('click', async () => {
  const response = await fetch('/node/query', {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      'X-Session-Id': vizqlServerSessionId,
      'Global-Session-Header': globalSessionHeader
    },
  });
  const data = await response.json();
  document.getElementById('results').textContent = JSON.stringify(data, null, 2);
});

Note: The credentials: 'include' option is required to ensure the browser includes cookies when calling its own origin.

Step 7 — Deploy and verify

  1. Deploy the app to Heroku.
  2. Open your app URL in a Chrome browser and open the Chrome DevTools.
  3. Load the embedded viz. These instructions assume you have configured your web app as a connected app, and have generated and provided the JWT to authenticate and load the embedded viz.
  4. Click the query button in the web app to call VDS.
  5. In Chrome DevTools, open the Network tab, find the request to your /node/query endpoint, and inspect the Cookie header. The workgroup_session_id (or Tableau session) cookie domain should match your app domain. This confirms that cookie sharing is working correctly.

Set up reverse proxy for Tableau Server

Setting up a reverse proxy is the most reliable way to enable the embedded viz and the VDS calls to share the same Tableau session cookie. This section shows an example setup that uses Nginx to place the Tableau Server, a web server (Node.js), and the VDS API calls all under one public hostname. All hosts must be within the same network. DNS configuration is managed by your administrator. See How the reverse proxy works for more information about reverse proxies.

Prerequisites

Step 1 — Configure Tableau Server for the reverse proxy

Run the following TSM commands to configure Tableau Server to accept requests through the proxy (replace analytics.example.com with the name of your host):

tsm configuration set -k gateway.public.host -v "analytics.example.com"
tsm configuration set -k gateway.public.port -v 443
tsm configuration set -k gateway.trusted -v <ip-address-of-nginx-server>
tsm configuration set -k gateway.trusted_hosts -v "analytics.example.com"
tsm pending-changes apply

Configure SSL on Tableau Server using the same certificates as the proxy. For more information, see Configure SSL for External HTTP Traffic to and from Tableau Server and Configure Tableau Server to Work with a Reverse Proxy Server.

Step 2 — Configure the Node.js web server

The Node.js server needs to:

  1. Serve index.html with the embedded viz.
  2. Expose an API endpoint (for example, GET /api/query) that reads the Tableau session cookie forwarded from the browser and uses it to call VDS on Tableau Server.

Make sure the Node.js host can resolve the hostname of the Tableau Server, either via an /etc/hosts entry or DNS lookup.

Step 3 — Configure Nginx

Configure Nginx at analytics.example.com with the following routing rules:

# Route all Tableau Server requests (viz, auth, REST API)
location / {
    proxy_pass https://<tableau-server-ip>;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# Route the embedding app
location /app/ {
    proxy_pass https://<embedding-server-ip>/;
    proxy_set_header Host $host;
}

# Route VDS calls to the Node.js backend
location /app/api/ {
    proxy_pass https://<embedding-server-ip>/api/;
    proxy_set_header Host $host;
}

Important: Tableau Server sign-in redirects to /sign_in. Make sure your Nginx configuration includes a location entry that handles this redirect path. Missing this is a common configuration failure point.

Step 4 — Build the frontend

Make the VDS call using credentials: 'include' to ensure the browser sends the session cookie:

fetch('/app/api/query', { credentials: 'include' })
  .then(res => res.json())
  .then(data => console.log(data));

Note: On Tableau Server, you could also use credentials: 'same-origin' to ensure the browser sends the session cookie.

Step 5 — Verify

In Chrome DevTools, open the Network tab. Find the /app/api/query request and confirm that the Cookie header contains workgroup_session_id with domain analytics.example.com.


If your web app and Tableau Server are on different hosts, but in same domain, you can configure Tableau to set the session cookie on a wildcard domain. This allows all subdomains under a shared parent to receive the cookie automatically, without needing a reverse proxy for the back-end server.

Run the following TSM commands (replace .example.com with the parent domain of your network. Be sure to include the leading period (.)).

tsm configuration set -k vizportal.session_cookie_domain -v ".example.com"
tsm pending-changes apply

With this configuration, cookies set by tableau.example.com are automatically available to webapp.example.com and any other *.example.com subdomain. Some configurations that have been tested and confirmed to work:

Web app server Tableau Server Cookie domain setting Result
test.example.com analytics.example.com .example.com Works
test.analytics.example.com analytics.example.com analytics.example.com Works
test.example.com analytics.otherdomain.com .example.com Does not work

Note: This TSM setting is available for Tableau Server only. It is not available for Tableau Cloud.


How the reverse proxy works

The most reliable solution for both Tableau Cloud and Tableau Server is to use a reverse proxy. The proxy makes all browser requests—the embedded viz and VDS API calls—appear to originate from a single domain. The proxy routes each request to the correct backend server. Because the browser sees only one domain, the Tableau session cookie set by the embedded viz is automatically included on subsequent VDS calls.

Reverse Proxy Requirements

The proxy must route three types of requests:

Node.js backend

How the request flow works

flowchart LR B["Browser\n(your_app.com)"] RP[Reverse proxy] N1["Node.js back-end server\n(serves index.html)"] TC["Tableau Cloud or Tableau Server\n(sets session cookie scoped\nto your_app.com)"] N2["Node.js backend\n(calls VDS with cookie,\nreturns result to browser)"] B -- "GET /" --> RP --> N1 B -- "GET /views/..." --> RP --> TC B -- "POST /node/query\n(sends cookie automatically:\nsame domain)" --> RP --> N2