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 = [