Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions solutions/reuse-token-rest-api-and-embed-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.env
*.local
.DS_Store
1 change: 1 addition & 0 deletions solutions/reuse-token-rest-api-and-embed-sdk/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
78 changes: 78 additions & 0 deletions solutions/reuse-token-rest-api-and-embed-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!-- search-meta
tags: [reuse-token, cookieless-auth, TrustedAuthTokenCookieless, REST-API-SDK, LiveboardEmbed, React, TypeScript]
apis: [ThoughtSpotRestApi, createBearerAuthenticationConfig, getFullAccessToken, AuthType, LiveboardEmbed, searchMetadata]
questions:
- How do I share one auth token between the REST API SDK and the Visual Embed SDK?
- How do I build a liveboard list page with the REST API SDK that opens a Liveboard embed on click?
- How do I avoid minting a separate token for cookieless embedding and REST API calls?
-->

# Reuse token between REST API SDK & Visual Embed SDK

A Liveboard list page built with the **REST API SDK** (TypeScript). Clicking a liveboard opens it with **`LiveboardEmbed`** using cookieless auth — both use the exact same cached token instead of each fetching their own.

Two equivalent frontends share one token server:

- `api/` — the token server, common to both.
- `vanilla/` — plain TypeScript + Vite, no framework.
- `react/` — the same flow with React.

## Key usage

```ts
// One token-fetcher, shared everywhere.
let cached: Token | null = null;
const getCachedAuthToken = async () => {
if (cached && cached.expiration_time_in_millis - Date.now() > 30_000) {
return cached.token;
}
const res = await fetch("/api/thoughtspot-token");
cached = await res.json();
return cached.token;
};

// REST API SDK client — used for the liveboard list.
const restClient = new ThoughtSpotRestApi(
createBearerAuthenticationConfig(THOUGHTSPOT_HOST, getCachedAuthToken),
);

// Embed SDK — used for the liveboard view. Same function, same token.
init({
thoughtSpotHost: THOUGHTSPOT_HOST,
authType: AuthType.TrustedAuthTokenCookieless,
getAuthToken: getCachedAuthToken,
});
```

## Run it

```bash
cp sample.env .env # shared by the token server and both frontends
npm install # installs the token server's deps (api/)

cd vanilla # or: cd react
npm install
npm run start # runs the shared token server + the Vite dev server together
```

Open http://localhost:3001 for `vanilla/`, or http://localhost:3000 for `react/`.

`sample.env` ships with a public ThoughtSpot training-instance demo user — fill in `DEMO_USER_PASSWORD`. For your own cluster, set `VITE_THOUGHTSPOT_HOST`, `VITE_DEMO_USER_USERNAME`, and either `DEMO_USER_PASSWORD` or `VITE_THOUGHTSPOT_SECRET_KEY` (recommended for production — see [Trusted authentication](https://developers.thoughtspot.com/docs/trusted-auth-secret-key)).

### Troubleshooting

- **`ECONNREFUSED` on `/api/thoughtspot-token` right after `npm run start`** — harmless startup race: Vite's dev server boots faster than the token server. Reload once you see `Token server listening on http://localhost:4000` in the terminal.
- **`DEPTH_ZERO_SELF_SIGNED_CERT`** — your network (often a corporate VPN) is TLS-intercepting with its own root cert, so Node rejects the ThoughtSpot cluster's certificate. Uncomment `NODE_TLS_REJECT_UNAUTHORIZED=0` in `.env` (dev-only, never in production).

## Files

- `api/token-server.ts` — Express endpoint (shared by both apps) that mints one bearer token via `getFullAccessToken`.
- `react/src/` / `vanilla/src/` — each has its own:
- `get-auth-token.ts` — caches that token in memory and re-fetches only once it's near expiry.
- `thoughtspot-client.ts` — REST API SDK client, authenticated with the cached token.
- `App.tsx` / `main.ts` — liveboard list (REST API SDK) → click → `LiveboardEmbed` (cookieless, same cached token).

## Documentation

- [Cookieless authentication](https://developers.thoughtspot.com/docs/trusted-auth-sdk#_cookieless_authentication_examples)
- [REST API SDK](https://developers.thoughtspot.com/docs/rest-api-sdk)
58 changes: 58 additions & 0 deletions solutions/reuse-token-rest-api-and-embed-sdk/api/token-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import express from "express";
import {
createBasicConfig,
ThoughtSpotRestApi,
} from "@thoughtspot/rest-api-sdk";

const app = express();

const PORT = process.env.VITE_SERVER_PORT || 4000;
const THOUGHTSPOT_HOST = (
process.env.VITE_THOUGHTSPOT_HOST || "https://training.thoughtspot.cloud"
).replace(/\/+$/, "");
const DEMO_USER_PASSWORD = process.env.DEMO_USER_PASSWORD;
const SECRET_KEY = process.env.VITE_THOUGHTSPOT_SECRET_KEY;

let thoughtspotClient: ThoughtSpotRestApi;
const getThoughtSpotClient = () => {
if (!thoughtspotClient) {
const basicClientConfig = createBasicConfig(THOUGHTSPOT_HOST);
thoughtspotClient = new ThoughtSpotRestApi(basicClientConfig);
}
return thoughtspotClient;
};

app.use(express.json());

// Issues the one token that both the REST API SDK client and the cookieless
// Embed SDK auth use — see src/get-auth-token.ts for how it's cached and reused.
app.get("/api/thoughtspot-token", async (req, res) => {
try {
const username = req.headers["x-my-username"] as string;

const credentials = SECRET_KEY
? // In production use cases use a Secret key
{ secret_key: SECRET_KEY }
: { password: DEMO_USER_PASSWORD };

const client = getThoughtSpotClient();
const data = await client.getFullAccessToken({
username,
...credentials,
});

res
.status(200)
.json({
token: data.token,
expiration_time_in_millis: data.expiration_time_in_millis,
});
} catch (e) {
console.error(e);
res.status(500).json({ error: (e as Error).message });
}
});

app.listen(PORT, () => {
console.log("Token server listening on", `http://localhost:${PORT}`);
});
Loading
Loading