Vendia Node Authentication

On each Uni Node, users can access the underlying resources - data, files, etc. through a GraphQL API with HTTPS transport. Vendia allows node owners to control the API access and usage through API settings.

There are two ways to connect to a Node:

API Keys

To enable programmatic server-to-server access to the Node, Vendia provides the ability to dynamically create an unlimited number of API Key(s). An API Key may optionally have an expiration if the data access use-case is timebound.

Note: API Keys are secrets and should be stored securely. The API Key is only viewable at the time of creation.

Creating an API Key in Vendia

In order to create an API Key via the Vendia Console:

  1. Navigate to your Uni landing page.
  2. Choose the Node for which you would like to add an API Key and select "Manage Node" to navigate to Node settings.
  3. Select "Authorization"
  4. Click "Add API Key".

Creating an API Key via GraphQL mutation

API Keys can also be created via the GraphQL Explorer or programmatically (if you have already have a key configured).

mutation addApiKeyMutation {
  addVendia_ApiKey(
    input: { name: "my-api-key", expiry: "2024-01-01", status: ACTIVE }
    syncMode: NODE_LEDGERED
  ) {
    result {
      apiKey
      _id
    }
  }
}

Creating an API Key via the CLI

To learn how to set up the Vendia Share CLI see our CLI docs

share node add-api-key \
    --uni 'my-uni-name' \
    --node 'my-node-name' \
    --name 'my-api-key-name' \
    --expiry '2030-01-01'

Using an API Key

GraphQL API

The API Key should be sent via the Authorization header of each GraphQL request

curl 'https://xxxxxxx.execute-api.us-west-2.amazonaws.com/graphql/' \
    -H 'Authorization: MY_API_KEY_HERE' \
    -H 'content-type: application/json' \
    -H 'accept: application/json' \
    --data-raw '{"query":"query blocksQuery {  listVendia_BlockItems {    Vendia_BlockItems {      blockId      blockHash    }  }}","variables":null,"operationName":"blocksQuery"}' \
    --compressed

GraphQL WebSocket API

The API Key should be sent in the first message after connecting to the WebSocket

const subscription = `
 subscription onBlockChanges {
  onAddVendia_Block {
    error
    result {
      blockId
      transactions {
        _id
        mutations
      }
      _id
      status
    }
  }
}
`;

const webSocket = new WebSocket(
  "wss://xxxxxxx.execute-api.us-west-1.amazonaws.com/graphql", //Use your GraphQL WebSocket API here
  "graphql-transport-ws"
);

webSocket.addEventListener("message", function (event) {
  const data = JSON.parse(event.data);
  console.log("Message from server ", data);
  if (data?.type === "connection_ack") {
    console.log("Received connection_ack");
    console.log("Sending subscription...");
    // Send GraphQL Subscription after Authentication handshake
    webSocket.send(
      JSON.stringify({
        id: "4f4ac00c-8055-44ac-a048-c4c2c77573ce", // Note: should be unique per subscription
        type: "subscribe",
        payload: {
          key: "vxyo52", // Note: should be unique per subscription
          query: subscription,
          data: `{"query":"${subscription}","variables":{}}`,
        },
      })
    );
  }
});

// Send Authentication message
webSocket.addEventListener("open", function () {
  webSocket.send(
    JSON.stringify({
      type: "connection_init",
      payload: { apiKey: "MY_API_KEY_HERE" }, // Use your API Key here
    })
  );
});

With the Vendia SDK

const client = createVendiaClient({
  apiUrl: "https://xxxxxxx.execute-api.us-east-1.amazonaws.com/graphql/",
  websocketUrl: "wss://xxxxxxx.execute-api.us-west-1.amazonaws.com/graphql",
  apiKey: "MY_API_KEY_HERE",
});

const blocks = await client.blocks.list();
client.blocks.onAdd((block) => console.log("New block added:", block));

JWT Authentication

JSON Web Tokens (JWT) are an open, industry standard RFC 7519 method for representing claims securely between two parties. Enabling JWT Authentication for your Vendia Node allows for both client-to-server and server-to-server access. With this authentication method, you specify your external identity provider's public key details so that the Node can validate the incoming token.

Gathering your Provider's public key details

In order to configure JWT Authentication, you will need to gather some information from your preferred identity provider. Common identity providers include:

  • Okta
  • Auth0
  • AWS Cognito
  • Azure Active Directory

You will need to provide the following information from your identity provider:

JSON Web Token Key Set (JWKS) URL

The JSON Web Key Set (JWKS) is a set of keys containing the public keys used to verify any JSON Web Token (JWT).

Examples:

  • Okta -> https://{my-okta-domain}.okta.com/oauth2/{my-okta-api}/v1/keys
  • Auth0 -> https://{my-auth0-domain}.us.auth0.com/.well-known/jwks.json

Issuer

The "iss" (issuer) claim identifies the principal that issued the JWT

Tip: The Issuer is often the custom domain given to you by your identity provider

Examples:

  • Okta -> https://{my-okta-domain}.okta.com/oauth2/{my-okta-api}
  • Auth0 -> https://{my-auth0-domain}.us.auth0.com/

Audience

The "aud" (audience) claim identifies the recipients for whom the JWT is intended.

Tip: This value is typically customizable and unique per application that is associated with your identity provider.

Examples:

  • Okta -> api://{my-app}
  • Auth0 -> https://{my-app-domain}

Scopes (Optional) The "scope" claim identifies the custom resources to which your JWT has access.

Tip: OIDC Standard scopes include: openid, profile, email

Creating JWT Authentication in Vendia

In order to setup JWT Authentication via the Vendia Console:

  1. Navigate to your Uni landing page.
  2. Choose the Node that will have JWT Authentication and select "Manage Node" to navigate to the Node settings.
  3. Select "Authorization"
  4. Click "Add JWT"

Create JWT Authentication via GraphQL

JWT Authenication can also be created via the GraphQL Explorer or programmatically (if you have already have a different authentication method configured).

mutation addJwtAuthentication {
  addVendia_JwtAuthentication(
    input: {
      name: "my-okta-app"
      jwksUrl: "https://{my-okta-domain}.okta.com/oauth2/{my-okta-api}/v1/keys"
      issuer: "https://{my-okta-domain}.okta.com/oauth2/{my-okta-api}"
      audience: "api://{my-app}"
    }
    syncMode: NODE_LEDGERED
  ) {
    result {
      _id
    }
  }
}

Using JWT Authentication

GraphQL API

The JWT should be sent via the Authorization header of each GraphQL request, using the Bearer prefix

curl 'https://xxxxxxx.execute-api.us-west-2.amazonaws.com/graphql/' \
    -H 'Authorization: Bearer MY_JWT_HERE' \
    -H 'content-type: application/json' \
    -H 'accept: application/json' \
    --data-raw '{"query":"query blocksQuery {  listVendia_BlockItems {    Vendia_BlockItems {      blockId      blockHash    }  }}","variables":null,"operationName":"blocksQuery"}' \
    --compressed

GraphQL WebSocket API

The API Key should be sent in the first message after connecting to the WebSocket

const subscription = `
 subscription onBlockChanges {
  onAddVendia_Block {
    error
    result {
      blockId
      transactions {
        _id
        mutations
      }
      _id
      status
    }
  }
}
`;

const webSocket = new WebSocket(
  "wss://xxxxxxx.execute-api.us-west-1.amazonaws.com/graphql", //Use your GraphQL WebSocket API here
  "graphql-transport-ws"
);

webSocket.addEventListener("message", function (event) {
  const data = JSON.parse(event.data);
  console.log("Message from server ", data);
  if (data?.type === "connection_ack") {
    console.log("Received connection_ack");
    console.log("Sending subscription...");
    // Send GraphQL Subscription after Authentication handshake
    webSocket.send(
      JSON.stringify({
        id: "4f4ac00c-8055-44ac-a048-c4c2c77573ce", // Note: should be unique per subscription
        type: "subscribe",
        payload: {
          key: "vxyo52", // Note: should be unique per subscription
          query: subscription,
          data: `{"query":"${subscription}","variables":{}}`,
        },
      })
    );
  }
});

// Send Authentication message
webSocket.addEventListener("open", function () {
  webSocket.send(
    JSON.stringify({
      type: "connection_init",
      payload: { token: "MY_JWT_HERE" }, // Use your token here
    })
  );
});

With the Vendia SDK

const client = createVendiaClient({
  apiUrl: "https://xxxxxxx.execute-api.us-east-1.amazonaws.com/graphql/",
  websocketUrl: "wss://xxxxxxx.execute-api.us-west-1.amazonaws.com/graphql",

  // Use getCredentials to perform any async logic needed to request a JWT from your
  // auth provider - will be resolved prior to establishing socket connection or making an API call
  getCredentials: async () => {
    const token = await getTokenFromAuthProvider(); // Your custom logic to retrieve a token
    return { token };
  },
});

const blocks = await client.blocks.list();
client.blocks.onAdd((block) => console.log("New block added:", block));

API Credentials

API Credentials allow you to programmatically access both the Vendia Control Plane API (e.g. for listing and creating Unis) and Node APIs. Vendia API Credentials implement the OAuth Client Credentials flow and which exchanges the credentials for a short-lived access token.

To get started, navigate to your Organization page and select API Credentials in the left nav.

API Credentials Page

Click Create API Credentials and you can then specify a name, expiration, and role for your API Credentials.

API Credentials Create Page

After clicking the Save button you're presented with your Client ID and Client Secret, as well as instructions on how to use them with the Vendia SDK.

API Credentials Created Page
// Use VendiaShareSDK for managing Vendia resources
import VendiaShareSDK from '@vendia/share-sdk'
const vendiaShareSdk = new VendiaShareSDK({
  apiCredentials: {
    clientId: 'xxxxxxxxx-xxxxxxxxx-xxxx-xxxx-xxxxxxxxx',
    clientSecret: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxx'
  }
})
await vendiaShareSdk.listUnis()

// Use createVendiaClient for managing individual Uni/Node
import { createVendiaClient } from '@vendia/client'
const vendiaClient = createVendiaClient({
  // Copy GraphQL URL from Manage Node page
  apiUrl: 'https://___.execute-api.___.amazonaws.com/graphql/',
  websocketUrl: 'wss://___.execute-api.___.amazonaws.com/graphql', // optional
  apiCredentials: {
    clientId: 'xxxxxxxxx-xxxxxxxxx-xxxx-xxxx-xxxxxxxxx',
    clientSecret: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxx'
  }
})
await vendiaClient.blocks.list()

If you're not using the Vendia SDK, you can use the following flow to exchange your Client Credentials (Client ID and Client Secret) for an Access Token that can then be used to Access the Vendia APIs. For the best performance, cache your Access Token and only refresh it when it's expired or close to expiring.

import { decode } from 'jsonwebtoken'

let aliveAccessToken

async function vendiaApiRequest() {
  aliveAccessToken = isTokenExpired(aliveAccessToken) ? await refreshAccessToken(CLIENT_ID, CLIENT_SECRET) : aliveAccessToken

  return fetch(NODE_API_URL, {
    method: 'POST',
    headers: {
      Authorization = `Bearer ${aliveAccessToken.access_token}`,
      Accept: 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: `query blocksQuery {
        listVendia_BlockItems {
          Vendia_BlockItems {
            blockId
            blockHash
            }
          }
        }`,
      variables:null,
      operationName: 'blocksQuery'
    })
  })
}

async function refreshAccessToken(clientId, clientSecret) {
  const jsonBody = {
    grant_type: 'client_credentials',
    client_id: clientId,
    client_secret: clientSecret,
  }
  const urlEncodedBody = new URLSearchParams(Object.entries(jsonBody)).toString()
  const accessToken = await fetch('https://auth.share.vendia.com/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: urlEncodedBody,
  })
  return accessToken.json()
}

function isTokenExpired (accessToken) {
  try {
    if (!token?.length) {
      return true
    }

    const decodedJwt = decode(token, { complete: true })

    const payload = decodedJwt?.payload

    if (!payload?.exp) return true

    // Return true if the token is expired or about to expire (in ~5s)
    const tokenExpiration = payload.exp * 1000
    return tokenExpiration < Date.now() - 5000
  } catch (e) {
    console.warn('Unable to parse JWT', e)
    return true
  }
}