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
diff --git a/projects/client-api-react/src/index.js b/projects/client-api-react/src/index.js
index 9b1e7ea..b120cd9 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 = {
@@ -365,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 });
@@ -416,6 +418,26 @@ function generateIframeRef({
),
tap((g) => {
console.debug('new iframe all init updates handled, if any', g);
+
+ // 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);
if (props.onClientAPIConnected) {
console.debug('has onClientAPIConnected(), calling', props.onClientAPIConnected);
@@ -465,13 +487,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 = props.client && props.client.authTokenValid() ? props.client._token : null;
+
const [g, setG] = useState(null);
const [gObs, setGObs] = useState(null);
const [gSub, setGSub] = useState(null);
@@ -523,9 +548,10 @@ const Graphistry = forwardRef((props, ref) => {
return {
g,
+ client,
...exportedCalls
};
- }, [g]);
+ }, [g, client, dataset]);
useEffect(() => {
if (g && onSelectionUpdate) {
@@ -583,7 +609,8 @@ const Graphistry = forwardRef((props, ref) => {
url, dataset, props,
axesMap,
iframeStyle, iframeClassName, iframeProps, allowFullScreen,
- tolerateLoadErrors
+ tolerateLoadErrors,
+ authToken
});
const children = [