From a04ea977846da607bffa285e159f4eadd517bf46 Mon Sep 17 00:00:00 2001 From: Desirree Adegunle <87389186+dess890@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:27:07 -0400 Subject: [PATCH 1/4] fea(index): initialized graphistry client-api class and login using the auth token --- projects/client-api-react/src/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/projects/client-api-react/src/index.js b/projects/client-api-react/src/index.js index 9b1e7ea..b6fa117 100644 --- a/projects/client-api-react/src/index.js +++ b/projects/client-api-react/src/index.js @@ -104,7 +104,8 @@ const propTypes = { onLabelsUpdate: PropTypes.func, selectionUpdateOptions: PropTypes.object, - queryParamExtra: PropTypes.object + queryParamExtra: PropTypes.object, + client: PropTypes.object }; const defaultProps = { @@ -465,13 +466,16 @@ const Graphistry = forwardRef((props, ref) => { onUpdateObservableG, onSelectionUpdate, onLabelsUpdate, - selectionUpdateOptions + selectionUpdateOptions, + client } = props; const [loading, setLoading] = useState(!!props.loading); const [dataset, setDataset] = useState(props.dataset); const [loadingMessage, setLoadingMessage] = useState(props.loadingMessage || ''); + const authToken = client && client._token && client.authTokenValid() ? client._token : null; + const [g, setG] = useState(null); const [gObs, setGObs] = useState(null); const [gSub, setGSub] = useState(null); @@ -569,6 +573,10 @@ const Graphistry = forwardRef((props, ref) => { } } + if (authToken) { + extraParams += `&authToken=${encodeURIComponent(authToken)}`; + } + const url = `${graphistryHost || ''}/graph/graph.html${'' }?play=${playNormalized }&info=${showInfo From aede36dc562108d2872e8314482d11fb6025b8a8 Mon Sep 17 00:00:00 2001 From: Desirree Adegunle <87389186+dess890@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:46:00 -0400 Subject: [PATCH 2/4] fix(index): abstracts authtoken (doesnt pass into url params) --- projects/client-api-react/src/index.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/projects/client-api-react/src/index.js b/projects/client-api-react/src/index.js index b6fa117..f0a6b53 100644 --- a/projects/client-api-react/src/index.js +++ b/projects/client-api-react/src/index.js @@ -366,7 +366,8 @@ function generateIframeRef({ url, dataset, props, axesMap, iframeStyle, iframeClassName, iframeProps, allowFullScreen, - tolerateLoadErrors + tolerateLoadErrors, + authToken }) { console.debug('@generateIframeRef', { url, dataset, props, axesMap, iframeStyle, iframeClassName, iframeProps, allowFullScreen, tolerateLoadErrors }); @@ -417,6 +418,15 @@ function generateIframeRef({ ), tap((g) => { console.debug('new iframe all init updates handled, if any', g); + // for sending authToken to the iframe + if (authToken && iframe && iframe.contentWindow) { + console.debug('Sending auth token to iframe via postMessage'); + iframe.contentWindow.postMessage({ + type: 'auth', + authToken: authToken + }, '*'); + } + setFirstRun(false); if (props.onClientAPIConnected) { console.debug('has onClientAPIConnected(), calling', props.onClientAPIConnected); @@ -573,10 +583,6 @@ const Graphistry = forwardRef((props, ref) => { } } - if (authToken) { - extraParams += `&authToken=${encodeURIComponent(authToken)}`; - } - const url = `${graphistryHost || ''}/graph/graph.html${'' }?play=${playNormalized }&info=${showInfo @@ -591,7 +597,8 @@ const Graphistry = forwardRef((props, ref) => { url, dataset, props, axesMap, iframeStyle, iframeClassName, iframeProps, allowFullScreen, - tolerateLoadErrors + tolerateLoadErrors, + authToken }); const children = [ From 34a274148ca6a454b978113ad9520966714ac582 Mon Sep 17 00:00:00 2001 From: Desirree Adegunle <87389186+dess890@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:04:20 -0400 Subject: [PATCH 3/4] fea(readme): updated docs --- README.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1699ca9..4cf75e9 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,38 @@ import { Graphistry } from '@graphistry/client-api-react';` // + variants for di See [@graphistry/client-api-react project](projects/client-api-react/README.md), [interactive storybook docs](https://graphistry.github.io/graphistry-js/), and [Create React App project sample](projects/cra-test/README.md) +### Authentication with Client API + +For secure authentication, create a `Client` instance and pass it to your components: + +```javascript +import { Client, Dataset, EdgeFile, NodeFile } from '@graphistry/client-api'; +import { Graphistry } from '@graphistry/client-api-react'; + +// Create authenticated client +const client = new Client( + 'my_username', + 'my_password', + '', // org (optional) + 'https', // protocol + 'hub.graphistry.com' // host +); + +// Use with React component +return ( + +); +``` + +This approach provides: +- JWT-based authentication (vs deprecated API keys) +- Automatic token management and refresh +- Secure server-to-server communication +

## @graphistry/node-api @@ -140,9 +172,11 @@ To support server-acceleration and fast interactions, Graphistry decouples uploa - You can configure your Graphistry server to run as http, https, or both - Uploads require authentication + - **Recommended**: The `Client` class provides modern JWT-based authentication for both browser and Node.js environments - The `node-api` client already uses the new JWT-based protocol ("API 3") - - Deprecated: The clientside JavaScript convenience APIs still use the deprecrated "API 1" protocol (key-based), which lacks JWT-based authentication and authorization + - **Deprecated**: The clientside JavaScript convenience APIs still use the deprecated "API 1" protocol (key-based), which lacks JWT-based authentication and authorization - We recommend clients instead use `fetch` or other HTTP callers to directly invoke the REST API: See how the `node-api` performs it - The client JavaScript APIs will updated to the new JWT method alongside recent CORS and SSO updates; contact staff if you desire assistance + - **Deprecated**: Legacy API key authentication is still supported but will be phased out - Sessions are based on unguessable web keys: sharing a secret ID means sharing read access -- Datasets are immutable and thus their integrity is safe for sharing, while session state (e.g., filters) are writable: share a copy when in doubt +- Datasets are immutable and thus their integrity is safe for sharing, while session state (e.g., filters) are writable: share a copy when in doubt \ No newline at end of file From d14eb6b95213ee64596131979d32019426496a9c Mon Sep 17 00:00:00 2001 From: Desirree Adegunle <87389186+dess890@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:27:02 -0400 Subject: [PATCH 4/4] fix(auth): add client JWT auth support to React component --- projects/client-api-react/src/index.js | 30 ++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/projects/client-api-react/src/index.js b/projects/client-api-react/src/index.js index f0a6b53..b120cd9 100644 --- a/projects/client-api-react/src/index.js +++ b/projects/client-api-react/src/index.js @@ -418,13 +418,24 @@ function generateIframeRef({ ), tap((g) => { console.debug('new iframe all init updates handled, if any', g); - // for sending authToken to the iframe - if (authToken && iframe && iframe.contentWindow) { - console.debug('Sending auth token to iframe via postMessage'); - iframe.contentWindow.postMessage({ - type: 'auth', - authToken: authToken - }, '*'); + + // store JWT token for iframe auth + if (authToken && client.authTokenValid()) { + try { + localStorage.setItem('graphistry_auth_token', authToken); + console.info('Stored JWT token for iframe authentication'); + + if (iframe && iframe.contentWindow) { + const targetOrigin = new URL(props.graphistryHost).origin; + iframe.contentWindow.postMessage({ + type: 'auth-token-available', + agent: 'graphistryjs', + token: authToken + }, targetOrigin); + } + } catch (error) { + console.warn('Failed to store auth token:', error); + } } setFirstRun(false); @@ -484,7 +495,7 @@ const Graphistry = forwardRef((props, ref) => { const [dataset, setDataset] = useState(props.dataset); const [loadingMessage, setLoadingMessage] = useState(props.loadingMessage || ''); - const authToken = client && client._token && client.authTokenValid() ? client._token : null; + const authToken = props.client && props.client.authTokenValid() ? props.client._token : null; const [g, setG] = useState(null); const [gObs, setGObs] = useState(null); @@ -537,9 +548,10 @@ const Graphistry = forwardRef((props, ref) => { return { g, + client, ...exportedCalls }; - }, [g]); + }, [g, client, dataset]); useEffect(() => { if (g && onSelectionUpdate) {