Embed interactive analytics into your SaaS in no-time with Netlify, Cumul.io and Auth0

Stephanos Theodotou
20 min readOct 24, 2022

--

How do you extract spending patterns from your database to show to users in your FinTech app? Or how do you provide captivating visual analytics about the status of tasks in your project management app? If you’ve ever tried to extract meaningful analytics from your database and make them available to your end-users, you’d agree this is no easy task.

You’d need development and maintenance work to expose on your API and spend multiple sprints refining your front-end charts. Then, you’d need to integrate everything with your authentication layer to ensure that a users sees the right data and not somebody else’s.

Today, we’ll take the first step to adding analytics in our e-commerce site at record speed using Cumul.io’s embedded dashboards. We will offer our vendors (shoe brands) a portal to log-into and monitor the performance of their sales.

We will also use Netlify’s (serverless) Functions to deploy our backend quickly and remove the need to maintain any servers. From there, we will secure our multi-tenant app with Auth0 to ensure that brands only have access to their own sales data.

What we will be building

Our e-commerce shop, “Universal Footwear” sells shoes throughout the galaxy! As you’d expect, our site lists a range of brands from various planets. Our most notable brands include Earthly Shoes (from Earth) and Mars Boots (yeah, you guessed it, from planet Mars). Their sales executives have long been asking for detailed, real-time analytics about how well their products perform on our site. Today, we will do just that.

Prerequisites

First, let’s sign up and activate our accounts with the following services:

1. Set up you front-end with serverless functions

Netlify functions not only act as our complete backend solution, we can also maintain and run them from within the same directory as our front-end. This means we can easily maintain and debug our backend while benefiting from a one-click run/deploy flow.

Let’s get started by setting up our front-end first.

  1. Go ahead and create a new React app with create-react-app
npx create-react-app cumul-multi-tenant-analytics-with-netlify
cd cumul-multi-tenant-analytics-with-netlify
npm start

Your front-end app should now be up and running. You can now add a Netlify Function right next to your front-end code:

  • Create a new directory called functions outside your front-end source code and create a new file within it called fetch-sso.js.
  • Add a netlify.toml configuration file in the root directory. The TOML file will allow us to describe to Netlify how to best deploy both our functions and our front-end app.

Your file tree structure should look like this:

2. Test your serverless function locally

In netlify.toml, we need to indicate to Netlify where to find and run our functions. We can do this like so:

[functions]
directory="functions"
node_bundler="esbuild"

With the esbuild command, we are also informing Netlify that we would like to use their latest bundler, which will optimise our functions by ensuring that they are as small and fast as possible and allow us to use modern JavaScript syntax and features.

The fetch-sso file we have created will host our Netlify serverless function definition, which will serve us our backend. A Netlify serverless function is composed of an exported handler which always does two things:

  1. Receives information about the event triggered and the execution context in which it runs.
  2. Returns a JSON response like below.

You can copy this into fetch-sso.js:

exports.handler = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello World" }),
};
};

We can run this function locally and test it out. To do that, we need to use Netlify’s command line tool, netlify-cli. The tool will allow us to emulate what would happen if we deployed our app on Netlify’s life servers and interacted with a live serverless function. To use, first install globally with the following command:

npm install netlify-cli -g

Once installed, we can use the netlify dev command to run Netlify’s production routing engine within a local dev server on our machine. This will our serverless functions available locally. Netlify’s dev engine will run on our localhost:8888 if it’s not occupied. Let’s go ahead and try it out by running:

netlify dev

Netlify should now make the serverless function we’ve defined in fetch-sso.js available and accessible via our browser. To get to it, we just need to visit a url with the function’s file name as the endpoint. So in this case, if you visit http://localhost:8888/.netlify/functions/fetch-sso you will be able to see a message returned in the browser.

3. Why we need a serverless function

Great! We’ve run our first function. In practice, the fetch-sso function will be responsible for securely generating Cumul.io credentials with the Cumul.io SDK and serving them to the front-end Cumul.io dashboard. Doing so will ensure that only the logged-in users can see our dashboards and that they can read data that concerns only them.

So, for example, if the Marketing Director from Earthly Shoes wants to log into our platform to check their most popular products on our site, they will only see information about Earthly Shoes and not Mars Boots.

In our serverless function, we can dynamically decide which data to serve to which user by passing an authentication token to it after logging in. We will learn how to do that in Part 2 of this tutorial. In the meantime, let’s write our first serverless function:

4. Add the Auth0 React SDK

As you’ve seen, anyone who visits the /fetch-sso endpoint will be able to run our serverless function. Let’s ensure that only the right people can call it from our web app. We will use auth0 to add email and password login capabilities to our app. We are building a React App, so let’s go ahead and install the relevant SDK:

npm install @auth0/auth0-react

The Auth0 React SDK will help us manage the authentication state of our users. To only allow authenticated users to access particular pages, we need to wrap the Auth0Provider around our Routing component in App.js like below.

import React from "react";
import "./App.css";
import { Routing } from "./Routing";
import { Auth0Provider } from "@auth0/auth0-react";
function App() {
return (
<Auth0Provider
domain={process.env.REACT_APP_AUTH0_DOMAIN}
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
redirectUri={`${window.location.origin}/brand-analytics`}]
audience={process.env.REACT_APP_AUTH0_AUDIENCE}
scope="read:current_user"
>
<Routing />
</Auth0Provider>
);
}
export default App;

5. Configure Auth0 to connect to our app

As you can see, we need a few environment variables for the Auth0Provider to connect to our account. You can find these in your own Auth0 instance and add them to the .env file. To get these, you need to create a new Single Page Application in Auth0:

Once you have an app ready, get the domain and client ID from the auth0 application page. We won’t be using the client secret because we are building a SPA.

Because we are building a SPA, we won’t be using the client secret.

In the same Auth0 application page, under Application URIs, add our app’s URIs to the below:

  • Allowed callback URLs: http:localhost:8888/brand-analytics
  • Allowed logout URLs: http:localhost:8888
  • Allowed web origins: http:localhost:8888/brand-analytics

This will tell Auth0 to allow any calls coming from the above URL. The callback URL will also tell Auth0 where in our app they should redirect the user after logging in. Since we want users to access their brand’s analytics portal, as soon as Auth0 authenticates them, they will be redirected to page /brand-analytics

To get the audience, you need to head to the APIs section in your Auth0 account, select the Auth0 Management API and copy the API Audience value into the “audience” variable in Auth0Provider.

6. Add Web Routing

For routing, we’ll use the React Router V6 library to manage our users’ navigation between public and protected pages. Unlike other frontend frameworks, React’s strength is in state management and doesn’t come with its own page routing solution which means we need to install another library to handle this. RR is one of the most widely-used routing libraries for React with approximately 9 million downloads per week.

Our /brand-analytics page will be a protected page and contain the Cumul.io analytics dashboards and will only allow authenticated users from Auth0 to reach it.

Let’s have a look at how we can achieve this with React Router. First, let’s install RRv6 and define our initial routes in Routing.js:

npm install react-router-dom@6

Then add the following routes:

import React from "react";
import { Route, Routes } from "react-router-dom";
import BrandAnalytics from "./pages/BrandAnalytics";
import Public from "./pages/Public";
export const Routing = () => {
return (
<Routes>
<Route path="/" element={<Public />} />
<Route path="/brand-analytics" element={<BrandAnalytics />} />
</Routes>
);
};

7. Prepare the public page

For each route, we will create a separate page. We will use Material-UI’s component library to create the user interface for each page. Let’s go ahead and install its dependencies

npm install @mui/material @emotion/react @emotion/styled

The Public component will render the home page and will allow users to login. Paste the following template in ./src/pages/Public

import * as React from "react";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { LoginButton } from "../components/LoginButton";
function Public() {
return (
<Box sx={{ textAlign: "center", mt: "4rem" }}>
<Typography variant="h1">Universal Footwear</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
height: 300,
alignItems: "center",
justifyContent: "center",
}}
>
<Typography variant="body2" sx={{ mb: "2rem" }}>
Hey there! Login to your brand's portal to see analytics about your
products
</Typography>
<LoginButton />
</Box>
</Box>
);
}
export default Public;

8. Log users in and out of our app

If the user is not logged in, clicking the login button will redirect them to Auth0’s out-of-the-box authentication UI. Our button can easily do that by using the loginWithRedirect function from Auth0’s useAuth0 context hook. Let’s define a Login Button that does just that in a new directory called “components”:

import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import Button from "@mui/material/Button";
export const LoginButton = () => {
const { loginWithRedirect } = useAuth0();
const handleLogin = () => loginWithRedirect();
return <Button onClick={handleLogin}>Log In</Button>;
};

Similarly, let’s create the logout button that will take us back to the home page:

import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import Button from "@mui/material/Button";
const LogoutButton = () => {
const { logout } = useAuth0();
const handleLogout = () => logout({ returnTo: window.location.origin });
return <Button onClick={handleLogout}>Logout</Button>;
};
export default LogoutButton;

Now, if we navigate to the “/”, we should be able to see the UI we had defined in Public.js. When we click on the login button, we should be redirected to Auth0’s Universal Login Page to handle login for us.

9. Create users and add metadata

To log a user in, we must first create them in our system by going into our Auth0 account and selecting User Management → Users → Create New. Let’s create two Sales Directors, one for each of our brands: brad@mars-boots.com and angelina@earthly-shoes.com.

In Auth0 go to User Management → Users → Create New

We will use these two accounts to learn how to customise the Cumul.io dashboards UI and show data based on which user is viewing them later in this tutorial. Our system needs to know which brand the user belongs to and only show them data of that brand.

For each of our users, let’s add the following metadata in app_metadata under the “Details” tab on the user’s page in Auth0:

For brad@mars-boots.com

{
"cumulio" : {
"brand":"Mars Boots"
}
}

For angelina@earthly-shoes.com:

{
"cumulio" : {
"brand":"Earthly Shoes"
}
}

Metadata like this is typically created during user signup, but we are skipping this step since we only cover existing users in this tutorial.

After adding metadata to the user profiles, we now need a secure way to encode them in every auth token generated by Auth0. Doing so can only happen programmatically within Auth0’s environment via a mechanism called Auth0 Actions.

Head to Actions → Library from the Auth0 menu to create an action. While we could write one from scratch (an action is essentially a JavaScript function), Cumul.io provides us with a premade action that can run at every log in and encode the metadata from a user’s profile to the token.

To find the Cumul.io Auth0 Action, click on “Add Action” , search for Cumul.io and click on “Add Integration”.

As we will see later, the Cumul.io SDK will be able to retrieve the metadata from a user token that will get passed to it and render a dashboard with the relevant data for that user’s brand.

Then, head to Actions → Flows from the Auth0 menu and select the Login Flow. The Cumul.io action should now be available on the installed actions in the sidebar. Drag and drop the action in between the “Start” and “Complete” steps of the flow diagram and hit “Apply”.

10. Create the private analytics page

Before we can attempt to log a user in, we need to prepare the protected /brand-analytics page, a page that only authenticated users can land on. Add the following to ./src/pages/BrandAnalytics.js

import { useEffect, useState } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Navigate } from "react-router-dom";
import { NavBar } from "../components/NavBar";
const BrandAnalytics = () => {
const { user, isAuthenticated, isLoading, getAccessTokenSilently } = useAuth0();
if (isLoading) {
return <div>Loading ...</div>;
}
return isAuthenticated ? (
<>
<NavBar />
<div style={{ width: "100vw" }}>
<p>{user.email}</p>
</div>
</>
) : (
<Navigate to="/" replace={true} />
);
};
export default BrandAnalytics;

When we visit this page, if the user is logged in, we will be able to see their email (as it was recorded in auth0) or be redirected to the home page if not.

11. Embed a dashboard into the app

Now it’s time to embed the dashboard we created earlier. Let’s install the cumul.io dashboard component

npm install @cumul.io/react-cumulio-dashboard

and wrap it around a new component called CumulioWrapper which we can add to our component library. Add the following code to ./src/components/CumulioWrapper.js

import { CumulioDashboardComponent } from '@cumul.io/react-cumulio-dashboard';
import { useRef } from "react";
export const CumulioWrapper = ({ dashboardKey, dashboardToken }) => {
const ref = useRef(null);
return (
<CumulioDashboardComponent
ref={ref}
authKey={dashboardKey}
authToken={dashboardToken}
slug={process.env.DASHBOARD_SLUG}
switchScreenModeOnResize={false}
loaderSpinnerColor="rgb(0, 81, 126)"
loaderSpinnerBackground="rgb(236 248 255)"
itemsRendered={(e) => console.log("itemsRendered", e)}
></CumulioDashboardComponent>
);
};

As you can see, our dashboard component will need 3 variables:

  • the DASHBOARD_SLUG, which we already have from the previous step
  • the dashboardKey and dashboardToken, which will be generated by the backend (in our serverless Netlify functions) using the NodeJS cumul.io SDK. Let’s have a look at how we can do that next.

12. Get a Cumul.io dashboard

After logging into Cumul.io, go to dashboards and then go to your “Onboarding Dashboard”.

Next, click on “Integrate” on the top right of the nav bar to configure a new integration. In the first step of the integration wizard confirm that you want to use the Onboarding Dashboard in this integration configuration.

After confirming, copy the dashboard “SLUG” and paste in the .env file.

In step 2, click on the filter icon next to the dataset. In filters, select the “brand” column from the list, then select the “is-in” condition, and then click the fx button to create a parameter. Let’s also name the parameter “brand

We have created a parameter which will allow Cumul.io to dynamically filter the data in the dashboard based on which brand the user that is viewing the dashboard works for.

We’ll discuss more about how to dynamically filter data in our app in step 16. Meantime, let’s save the filters and move to step 3 of the integration.

From Step 3, copy the integrationId and add it to the .env file as we will need to pass that to Cumul.io SDK in the next section.

13. Generate Cumul.io dashboard credentials in a Netlify Function

First, let’s install the Cumul.io SDK:

npm install cumulio

Now, go to functions/fetch-sso.js and replace the previous code with the following snippet:

const Cumulio = require("cumulio");exports.handler = async (event, context) => {
let ssoResponse;
var client = new Cumulio({
api_key: process.env.CUMUL_KEY,
api_token: process.env.CUMUL_TOKEN,
host: "https://api.cumul.io",
});
const generateSSOcredentials = async () => {
return await client.create("authorization", {
integration_id: process.env.CUMUL_INTEGRATION_ID,
type: "sso",
expiry: "24 hours",
inactivity_interval: "10 minutes",
username: "exampleUser",
name: "Example User",
email: "example@example.com",
suborganization: "exampleUser",
role: "viewer",
});
};
try {
ssoResponse = await generateSSOcredentials();
} catch (err) {
console.log(err.message);
return {
statusCode: 500,
body: JSON.stringify({
message: "Something went wrong, please try again later",
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({ token: ssoResponse.token, key: ssoResponse.id }),
};
};

For the SDK to create our dashboard credentials (authKey and authToken), we need to provide it with the SDK credentials first. We can create a CUMUL_KEY and CUMUL_TOKEN by visiting our profile page and then going to API tokens in the cumul.io app.

When our function runs, it will generate the key and token pair our front-end dashboard component needs to consume. All we need to do is call the function every time a user visits a dashboard

14. Render a dashboard for logged-in users

In order to easily call our fetch-sso.js function from the front-end, let’s first create a handler using the fetch api. We can do that in a separate directory called function-invocations like so:

export const fetchSSO = async (accessToken) => {
let res = await fetch(
`${process.env.REACT_APP_BASE_URL}/.netlify/functions/fetch-sso`,
{
method: "GET",
}
);
return res.json();
};

We can import the handler into our BrandAnalytics page with the CumulioWrapper component like below. Upon loading the page, we will call the getDashboardCredentials function, which calls the fetchSSO handler and sets the credential pair (keyToken) in state. Once there, we can pass these to the CumulioWrapper component as props.

import { useEffect, useState } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { fetchSSO } from "../function-invocations/fetchSSO";
import { CumulioWrapper } from "../components/CumulWrapper";
import { Navigate } from "react-router-dom";
import { NavBar } from "../components/NavBar";
const BrandAnalytics = () => {
const { isAuthenticated, isLoading } = useAuth0();
const [keyToken, setKeyToken] = useState(null);
const getDashboardCredentials = async () => {
try {
let pair = await fetchSSO();
setKeyToken(pair);
} catch (err) {
console.log(err.message);
}
};
useEffect(() => {
getDashboardCredentials();
}, [getDashboardCredentials]);
if (isLoading) {
return <div>Loading ...</div>;
}
return isAuthenticated ? (
<>
<NavBar />
<div style={{ width: "100vw" }}>
{keyToken !== null && (
<CumulioWrapper
dashboardKey={keyToken.key}
dashboardToken={keyToken.token}
/>
)}
</div>
</>
) : (
<Navigate to="/" replace={true} />
);
};
export default BrandAnalytics;

We should now be able to log in, navigate to the /brand-analytics page and see our dashboard!

15. Add authentication to Netlify functions

Note that while via the front-end, only logged-in users can get to see our dashboard on page /brand-analytics, our Netlify function is not protected and can be called by anyone visiting its endpoint.

You can check this by logging out and going to http://localhost:8888/.netlify/functions/fetch-sso in private-browsing mode. You can see the returned key and token from the cumul.io SDK. That’s not what we want; let’s add authentication to our Netlify functions too.

To do that, we need to send an auth token to our function every time we call it. The token will also need to relate to a specific user, so we should rely on auth0 to create a token for our logged-in user.

Our function will receive the token and then validate that this token comes from the right source — i.e. our auth0 account. We will use an external package called serverless-jwt, which can help us retrieve and decode tokens. Let’s see this in action by installing serverless-jwt for Netlify functions:

npm install @serverless-jwt/netlify

Now, let’s create a utility file called auth.js and add this in a new “lib” folder within .netlify to indicate that we can reuse the it across any new Netlify functions we create:

There, add the following code:

const { NetlifyJwtVerifier } = require("@serverless-jwt/netlify");export const auth = NetlifyJwtVerifier({
issuer: `https://${process.env.REACT_APP_AUTH0_DOMAIN}/`,
audience: process.env.REACT_APP_AUTH0_AUDIENCE,
});

We should use the auth0 domain and audience earlier when creating the Auth0Provider. Next, we will import and wrap our fetch-sso function with the auth utility.

If we then wrap our Netlify function with our auth utility like shown below, the verifier will always intercept the request and look for a token to decode in the request’s authorisation headers.

If the verifier approves that a valid token by the relevant issuer and audience has been sent, it will allow the Netlify function code to evaluate; otherwise, it will return an “Unauthorised” response.

const Cumulio = require("cumulio");
const { auth } = require("../.netlify/lib/auth");
exports.handler = auth(async (event, context) => {
let ssoResponse;
var client = new Cumulio({
api_key: process.env.CUMUL_KEY,
api_token: process.env.CUMUL_TOKEN,
host: "https://api.cumul.io",
});
const generateSSOcredentials = async () => {
return await client.create("authorization", {
integration_id: process.env.CUMUL_INTEGRATION_ID,
type: "sso",
expiry: "24 hours",
inactivity_interval: "10 minutes",
username: "exampleUser",
name: "Example User",
email: "example@app.com",
suborganization: "exampleUser",
role: "viewer",
});
};
try {
ssoResponse = await generateSSOcredentials();
} catch (err) {
console.log(err.message);
return {
statusCode: 500,
body: JSON.stringify({
message: "Something went wrong, please try again later",
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({ token: ssoResponse.token, key: ssoResponse.id }),
};
});

Now that our function can receive and validate authentication tokens let’s look at generating some in the front-end with auth0’s help. We will make use of the getAccessTokenSilently function from the useAuth0 hook to generate an accessToken for the particular logged-in user and pass it to our fetchSSO handler:

import { useEffect, useState } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { fetchSSO } from "../function-invocations/fetchSSO";
import { CumulioWrapper } from "../components/CumulWrapper";
import { Navigate } from "react-router-dom";
import { NavBar } from "../components/NavBar";
const BrandAnalytics = () => {
const { isAuthenticated, isLoading, getAccessTokenSilently } = useAuth0();
const [keyToken, setKeyToken] = useState(null);
const getDashboardCredentials = async () => {
try {
let accessToken = await getAccessTokenSilently({
audience: process.env.REACT_APP_AUTH0_AUDIENCE,
});
let pair = await fetchSSO(accessToken);
setKeyToken(pair);
} catch (err) {
console.log(err.message);
}
};
useEffect(() => {
getDashboardCredentials();
}, [getDashboardCredentials]);
if (isLoading) {
return <div>Loading ...</div>;
}
return isAuthenticated ? (
<>
<NavBar />
<div style={{ width: "100vw" }}>
{keyToken !== null && (
<CumulioWrapper
dashboardKey={keyToken.key}
dashboardToken={keyToken.token}
/>
)}
</div>
</>
) : (
<Navigate to="/" replace={true} />
);
};
export default BrandAnalytics;

Then, in our fetchSSO handler we simply need to include the generated token in the authorization headers when we invoke our serverless function:

export const fetchSSO = async (accessToken) => {
let res = await fetch(
`${process.env.REACT_APP_BASE_URL}/.netlify/functions/fetch-sso`,
{
method: "GET",
headers: { Authorization: `Bearer ${accessToken}` },
}
);
return res.json();
};

Great, our serverless function is now protected! If you try to call the Netlify function by going to http://localhost:8888/.netlify/functions/fetch-sso without being logged-in, you should encounter the following error message:

16. Extract metadata from token

While our serverless function is now protected and only authenticated users can trigger the Cumul.io SDK, we still need to distinguish which users are coming from “Earthly Shoes” vs “Mars Boots” in order for the SDK to know which data to show for each brand.

To manage that, we need to access the Auth0 app_metadata, which we have added to the token using the Cumul.io Auth0 Action we had installed in step 9. Since our auth utility can intercept the token, let’s add the following code to it to help us decode the token’s internals

const {
NetlifyJwtVerifier,
removeNamespaces,
} = require("@serverless-jwt/netlify");
export const requireAuth = NetlifyJwtVerifier({
issuer: `https://${process.env.REACT_APP_AUTH0_DOMAIN}/`,
audience: process.env.REACT_APP_AUTH0_AUDIENCE,
mapClaims: (claims) => {
const user = removeNamespaces("<https://cumulio/>", claims);
return user;
},
});

Let’s break this down: We are adding a function to map out any claims found in the intercepted token. Claims in this case are name-value pairs that contain information about a user such as the app_metadata we had encoded into the token using the Cumul.io Auth0 Action.

Our action added a namespace to these pairs: “https://cumulio/”. The namespace’s purpose is to keep our custom claims from colliding with any reserved claims or claims from other resources we may wish to add to our user’s tokens. To make our claims easier to read, we can remove the namespace after decoding the token by using the removeNamespaces function from the @serverless-jwt/netlify package.

Doing so will allow us to easily get the Cumul.io claims in the fetch-sso function as they will now be passed to the context argument of our function. We can deconstruct them from context.identityContext and pass them on to Cumul.ioSDK to dynamically allow data to be fetched for the brand described in the claims. To do this, we need to pass a metadata object to the Cumul.io client SDK like below:

const Cumulio = require("cumulio");
const fetch = require("node-fetch");
const { requireAuth } = require("../.netlify/lib/auth");
export const handler = requireAuth(async (event, context) => {
const { claims } = context.identityContext;
let ssoResponse;
const client = new Cumulio({
api_key: process.env.CUMUL_KEY,
api_token: process.env.CUMUL_TOKEN,
host: "<https://api.cumul.io>",
});
try {
const generateSSOcredentials = async () => {
return await client.create("authorization", {
integration_id: process.env.CUMUL_INTEGRATION_ID,
type: "sso",
expiry: "24 hours",
inactivity_interval: "10 minutes",
username: "exampleUser",
name: "Example User",
email: "example@app.com",
suborganization: claims.brand,
role: "viewer",
metadata: {
brand: [claims.brand],
}
});
};
ssoResponse = await generateSSOcredentials();
} catch (err) {
console.log(err.message);
return {
statusCode: 500,
body: JSON.stringify({
message: "Something went wrong, please try again later",
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({ token: ssoResponse.token, key: ssoResponse.id }),
};
});

17. Fetch user info from Auth0 and pass to Cumul.io

Until now, we’ve been using placeholder values in place of user info we have passed to Cumul.io. We want to pass the actual user info to Cumul.io for multiple reasons. One is the ability to create custom alerts to inform the right people when certain thresholds are exceeded in our dashboards. Another would be the ability for users to leave comments on particular dashboards.

The Cumul.io SDK will normally use the username as a globally unique identifier that determines how many times a particular user has accessed a dashboard. The Cumul.io pricing model is based on the amount of Monthly Active Viewers and Designers, and having the ability to recognise a unique user is important to avoid getting charged for viewing the same dashboard from two or more devices.

...
username: "exampleUser",
name: "Example User",
email: "example@app.com",
...

Adding such user info to an auth token isn’t a great idea as while tokens are encoded, they are not encrypted and technically anyone can decode and read their contents. To get the above user data, we need to query the Auth0 API based on a unique user identifier which is always present by default in the token claims: the “sub” claim:

const Cumulio = require("cumulio");
const fetch = require("node-fetch");
const { requireAuth } = require("../.netlify/lib/auth");
export const handler = requireAuth(async (event, context) => {
const { claims, token } = context.identityContext;
let ssoResponse;
const userRes = await fetch(
`https://${process.env.REACT_APP_AUTH0_DOMAIN}/api/v2/users/${claims.sub}`,
{
headers: new fetch.Headers({
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}),
}
);

const user = await userRes.json()
...

To access the Auth0 API in the first place however, we need to pass a valid token for that specific user. That shouldn’t be difficult as we are already sending the token to our serverless function from the front end. The token can be deconstructed from context.identityContext, the same argument object where we get our claims from.

Lastly, in order to call the endpoint we will need to user the Fetch API. Since at the time of writing native support for the Fetch API in NodeJS is experimental, we will use the node-fetch package instead. Just go ahead and install with

npm install node-fetch

The full code of our fetch-sso.js function should now look like this (note how we are passing the user info to Cumul.io):

const Cumulio = require("cumulio");
const fetch = require("node-fetch");
const { requireAuth } = require("../.netlify/lib/auth");
export const handler = requireAuth(async (event, context) => {
const { claims, token } = context.identityContext;
let ssoResponse;
const userRes = await fetch(
`https://${process.env.REACT_APP_AUTH0_DOMAIN}/api/v2/users/${claims.sub}`,
{
headers: new fetch.Headers({
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}),
}
);
const user = await userRes.json(); const client = new Cumulio({
api_key: process.env.CUMUL_KEY,
api_token: process.env.CUMUL_TOKEN,
host: "<https://api.cumul.io>",
});
try {
const generateSSOcredentials = async () => {
return await client.create("authorization", {
integration_id: "da65ca62-fd24-4a54-8a00-6434dea90d7b",
type: "sso",
expiry: "24 hours",
inactivity_interval: "10 minutes",
username: user.user_id,
name: user.name,
email: user.email,
suborganization: claims.brand,
role: "viewer",
metadata: {
brand: [claims.brand],
},
theme: {
id: claims.theme,
type: "foo",
itemsBackground: "#fff",
colors: ["#fff"],
},
});
};
ssoResponse = await generateSSOcredentials();
} catch (err) {
console.log(err.message);
return {
statusCode: 500,
body: JSON.stringify({
message: "Something went wrong, please try again later",
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({ token: ssoResponse.token, key: ssoResponse.id }),
};
});

We can now fetch the user info from Auth0 and pass to Cumul.io.

🚨 Check that the Auth0Provider component in App.js in the front end comes is defined with scope=”read:current_user”. Without this, the token will not have the necessary permissions to fetch the user details from Auth0’s API.

18. Prepare for production — Configure redirects

Before deploying to Netlify, we need to add configuration in our netlify.toml file to ensure our Single Page App routing works as expected:

[functions]
directory="functions"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

19. Prepare for production — Check for warnings

The Netlify build tools respect the variable CI=true, which means that your builds will fail if you have any warning messages in your console. If you are in a hurry to see a published version of your site and can’t correct the warning messages, you can bypass this behaviour by including the following command in your netlify.toml:

[functions]
directory="functions"
[build]
command = "CI=false && npm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

21. Prepare for production — Update URLs in Auth0

Once deployed, you’ll also need to tell Auth0 about the live URL of your app. Add the live URL to the following inputs in Applications → <your auth0 app> → Settings → Application URIs:

  • Allowed Callback URLs
  • Allowed Logout URLs
  • Allowed Web Origins

--

--

Stephanos Theodotou
Stephanos Theodotou

Written by Stephanos Theodotou

I'm a web developer and product manager merging code with prose and writing about fascinating things I learn.

No responses yet