<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krystian Fras</title>
    <description>The latest articles on DEV Community by Krystian Fras (@krystian50).</description>
    <link>https://dev.to/krystian50</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F594946%2Fbda19676-6a17-4f84-b38a-c231759bb319.jpeg</url>
      <title>DEV Community: Krystian Fras</title>
      <link>https://dev.to/krystian50</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krystian50"/>
    <language>en</language>
    <item>
      <title>Multi-Tenant Analytics with Auth0 and Cube.js 🔐 — the Complete Guide</title>
      <dc:creator>Krystian Fras</dc:creator>
      <pubDate>Fri, 12 Mar 2021 17:19:43 +0000</pubDate>
      <link>https://dev.to/cubejs/multi-tenant-analytics-with-auth0-and-cube-js-the-complete-guide-31lo</link>
      <guid>https://dev.to/cubejs/multi-tenant-analytics-with-auth0-and-cube-js-the-complete-guide-31lo</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: In this guide, we'll learn how to secure web applications with industry-standard and proven authentication mechanisms such as JSON Web Tokens, JSON Web Keys, OAuth 2.0 protocol. We'll start with an openly accessible, insecure analytical app and walk through a series of steps to turn it into a secure, multi-tenant app with role-based access control and an external authentication provider. We'll use &lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; to build an analytical app and Auth0 to authenticate users.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Security... Why bother? 🤔
&lt;/h1&gt;

&lt;p&gt;That's a fair question! As a renowned security practitioner George Orwell coined, "All users are equal, but some users are more equal than others."&lt;/p&gt;

&lt;p&gt;Usually, the need to secure an application is rooted in a premise that some users should be allowed to do more things than others: access an app, read or update data, invite other users, etc. To satisfy this need, an app should implement &lt;a href="https://www.mayurpahwa.com/2018/06/identification-authentication.html" rel="noopener noreferrer"&gt;IAAA&lt;/a&gt;, i.e., it should be able to perform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identification.&lt;/strong&gt; Ask users "Who are you?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication.&lt;/strong&gt; Check that users really are who they claim to be&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization.&lt;/strong&gt; Let users perform certain actions based on who they are &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability.&lt;/strong&gt; Keep records of users' actions for future review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, we'll go through a series of simple, comprehensible steps to secure a web app, implement IAAA, and user industry-standard mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 0.&lt;/strong&gt; Bootstrap an openly accessible analytical app with Cube.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 1.&lt;/strong&gt; Add &lt;em&gt;authentication&lt;/em&gt; with signed and encrypted JSON Web Tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2.&lt;/strong&gt; Add &lt;em&gt;authorization&lt;/em&gt;, multi-tenancy, and role-based access control with security claims which are stored in JSON Web Tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3.&lt;/strong&gt; Add &lt;em&gt;identification&lt;/em&gt; via an external provider with Auth0 and use JSON Web Keys to validate JSON Web Tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4.&lt;/strong&gt; Add &lt;em&gt;accountability&lt;/em&gt; with audit logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 5.&lt;/strong&gt; Feel great about building a secure app 😎&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Also, here's the &lt;a href="https://multi-tenant-analytics-demo.cube.dev" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; you can try right away.&lt;/strong&gt; It looks and feels exactly like the app we're going to build., i.e., it lets you authenticate with Auth0 and query an analytical API. And as you expected, the source code is on &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/multi-tenant-analytics" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Okay, let's dive in — and don't forget to wear a mask! 🤿&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 0. Openly accessible analytical app
&lt;/h1&gt;

&lt;p&gt;To secure a web application, we need one. So, we'll use &lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; to create an analytical API as well as a front-end app that talks to API and allows users to access e-commerce data stored in a database.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cube-js" rel="noopener noreferrer"&gt;
        cube-js
      &lt;/a&gt; / &lt;a href="https://github.com/cube-js/cube" rel="noopener noreferrer"&gt;
        cube
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      📊  Cube — The Semantic Layer for Building Data Applications
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; is an open-source analytical API platform that allows you to create an API over any database and provides tools to explore the data, help build a data visualization, and tune the performance. Let's see how it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first step is to create a new Cube.js project.&lt;/strong&gt; Here I assume that you already have &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; installed on your machine. Note that you can also &lt;a href="https://cube.dev/docs/getting-started-docker?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;use Docker&lt;/a&gt; with Cube.js. Run in your console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cubejs-cli create multi-tenant-analytics &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have your new Cube.js project in the &lt;code&gt;multi-tenant-analytics&lt;/code&gt; folder which contains a few files. Let's navigate to this folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second step is to add database credentials to the &lt;code&gt;.env&lt;/code&gt; file.&lt;/strong&gt; Cube.js will pick up its configuration options from this file. Let's put the credentials of a demo e-commerce dataset hosted in a cloud-based Postgres database. Make sure your &lt;code&gt;.env&lt;/code&gt; file looks like this, or specify your own credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cube.js environment variables: https://cube.dev/docs/reference/environment-variables
&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;demo-db.cube.dev&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5432&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_SSL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_USER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cube&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_PASS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;12345&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_NAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ecom&lt;/span&gt;

&lt;span class="py"&gt;CUBEJS_DEV_MODE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_WEB_SOCKETS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The third step is to start Cube.js API.&lt;/strong&gt; Run in your console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, our analytical API is ready! Here's what you should see in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcbsbclyriboyw0lm4ci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcbsbclyriboyw0lm4ci.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note it says that currently the API is running in development mode, so authentication checks are disabled. It means that it's openly accessible to anyone. We'll fix that soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fourth step is to check that authentication is disabled.&lt;/strong&gt; Open &lt;code&gt;http://localhost:4000&lt;/code&gt; in your browser to access Developer Playground. It's a part of Cube.js that helps to explore the data, create front-end apps from templates, etc.&lt;/p&gt;

&lt;p&gt;Please go to the "Schema" tab, tick &lt;code&gt;public&lt;/code&gt; tables in the sidebar, and click &lt;code&gt;Generate Schema&lt;/code&gt;. Cube.js will generate a &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;data schema&lt;/a&gt; which is a high-level description of the data in the database. It allows you to send domain-specific requests to the API without writing lengthy SQL queries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frainawhcpq2rs4sy7apj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frainawhcpq2rs4sy7apj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's say that we know that e-commerce orders in our dataset might be in different statuses (&lt;em&gt;processing&lt;/em&gt;, &lt;em&gt;shipped&lt;/em&gt;, etc.) and we want to know how many orders belong to each status. You can select these measures and dimensions on the "Build" tab and instantly see the result. Here's how it looks after the &lt;code&gt;Orders.count&lt;/code&gt; measure and the &lt;code&gt;Orders.status&lt;/code&gt; dimension are selected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71hpkkkb7683voabhwc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71hpkkkb7683voabhwc5.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works because Developer Playground sends requests to the API. So, you can get the same result by running the following command in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:4000/cubejs-api/v1/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'query={"measures": ["Orders.count"], "dimensions": ["Orders.status"]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.data'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that it employs the &lt;code&gt;jq&lt;/code&gt; utility, a command-line &lt;a href="https://stedolan.github.io/jq/tutorial/" rel="noopener noreferrer"&gt;JSON processor&lt;/a&gt;, to beautify the output. You can &lt;a href="https://stedolan.github.io/jq/download/" rel="noopener noreferrer"&gt;install&lt;/a&gt; &lt;code&gt;jq&lt;/code&gt; or just remove the last line from the command. Anyway, you'll get the result you're already familiar with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufukvu1arvo6x314z5wu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufukvu1arvo6x314z5wu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;‼️ &lt;strong&gt;We were able to retrieve the data without any authentication.&lt;/strong&gt; No security headers were sent to the API, yet it returned the result. So, we've created an openly accessible analytical API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The last step is to create a front-end app.&lt;/strong&gt; Please get back to Developer Playground at &lt;code&gt;http://localhost:4000&lt;/code&gt;, go to the "Dashboard App" tab, choose to "Create your Own" and accept the defaults by clicking "OK". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43ljijihw21cpknz4i22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43ljijihw21cpknz4i22.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In just a few seconds you'll have a newly created front-end app in the &lt;code&gt;dashboard-app&lt;/code&gt; folder. Click "Start dashboard app" to run it, or do the same by navigating to the &lt;code&gt;dashboard-app&lt;/code&gt; folder and running in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a front-end app like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fseqbbxsskkwlccjfvcmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fseqbbxsskkwlccjfvcmy.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you go to the "Explore" tab, select the &lt;code&gt;Orders Count&lt;/code&gt; measure and the &lt;code&gt;Orders Status&lt;/code&gt; dimension once again, you'll see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpelpjdemi8bc9nk2ma1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpelpjdemi8bc9nk2ma1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That means that we've successfully created a front-end app that makes requests to our insecure API. You can also click the "Add to Dashboard" button to persist this query on the "Dashboard" tab.&lt;/p&gt;

&lt;p&gt;Now, as we're navigating some dangerous waters, it's time to proceed to the next step and add authentication 🤿 &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1. Authentication with JWTs
&lt;/h1&gt;

&lt;p&gt;As we already know, the essence of authentication is making sure that our application is accessed by verified users, and not by anyone else. How do we achieve that?&lt;/p&gt;

&lt;p&gt;We can ask users to pass a piece of information from the web application to the API. If we can verify that this piece of information is valid and it passes our checks, we'll allow that user to access our app. Such a piece of information is usually called a &lt;em&gt;token&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;JSON Web Tokens&lt;/a&gt; are an open, industry-standard method for representing such pieces of information with additional information (so-called &lt;em&gt;claims&lt;/em&gt;). Cube.js, just like many other apps, uses JWTs to authenticate requests to the API.&lt;/p&gt;

&lt;p&gt;Now, we're going to update the API to authenticate the requests and make sure the web application sends the correct JWTs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, let's update the Cube.js configuration.&lt;/strong&gt; In the &lt;code&gt;.env&lt;/code&gt; file, you can find the following options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;CUBEJS_DEV_MODE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first option controls if Cube.js should run in the &lt;a href="https://cube.dev/docs/configuration/overview?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#development-mode" rel="noopener noreferrer"&gt;development mode&lt;/a&gt;. In that mode, all authentication checks are disabled. The second option sets the key used to cryptographically sign JWTs. It means that, if we keep this key secret, only we'll be able to generate JWTs for our users.&lt;/p&gt;

&lt;p&gt;Let's update these options (and add a new one, described in &lt;a href="https://cube.dev/docs/reference/environment-variables?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#general" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CUBEJS_DEV_MODE=false
CUBEJS_API_SECRET=NEW_SECRET
CUBEJS_CACHE_AND_QUEUE_DRIVER=memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of &lt;code&gt;NEW_SECRET&lt;/code&gt;, you should generate and use a new pseudo-random string. One way to do that might be to use an &lt;a href="https://www.uuidgenerator.net" rel="noopener noreferrer"&gt;online generator&lt;/a&gt;. Another option is to run this simple Python command in your console and copy-paste the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import sys,uuid; sys.stdout.write(uuid.uuid4().hex)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, save the updated &lt;code&gt;.env&lt;/code&gt; file, stop Cube.js (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), and run Cube.js again with &lt;code&gt;npm run dev&lt;/code&gt;. You'll see a message without mentioning the Development Mode in the console and Developer Playground will no longer be present at &lt;a href="https://localhost:4000" rel="noopener noreferrer"&gt;localhost:4000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, let's check that the web application is broken. 🙀&lt;/strong&gt; It should be because we've just changed the security key and didn't bother to provide a correct JWT. Here's what we'll see if we repeat the &lt;code&gt;curl&lt;/code&gt; command in the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f1kc9s31vorfq2zahf8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f1kc9s31vorfq2zahf8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks legit. But what's that "Authorization header", exactly? It's an HTTP header called &lt;code&gt;Authorization&lt;/code&gt; which is used by Cube.js to &lt;a href="https://cube.dev/docs/rest-api?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#prerequisites-authentication" rel="noopener noreferrer"&gt;authenticate&lt;/a&gt; the requests. We didn't pass anything like that via the &lt;code&gt;curl&lt;/code&gt; command, hence the result. And here's what we'll see if we reload our web application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feung09ctqch0ckliwxny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feung09ctqch0ckliwxny.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Indeed, it's broken as well. Great, we're going to fix it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, let's generate a new JWT and fix the web application.&lt;/strong&gt; You can use lots of &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;libraries&lt;/a&gt; to work with JWTs, but Cube.js provides a convenient way to generate tokens in the command line. Run the following command, substituting &lt;code&gt;NEW_SECRET&lt;/code&gt; with your key generated on the first step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cubejs-cli token &lt;span class="nt"&gt;--secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"NEW_SECRET"&lt;/span&gt; &lt;span class="nt"&gt;--payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"role=admin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o1eqezymjulb4dud62p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2o1eqezymjulb4dud62p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output provides the following insights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We've created a new JWT: &lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJ1Ijp7fSwiaWF0IjoxNjE1MTY1MDYwLCJleHAiOjE2MTc3NTcwNjB9.IWpKrqD71dkLxyJRuiii6YEfxGYU_xxXtL-l2zU_VPY&lt;/code&gt; (your token should be different because your key is different).&lt;/li&gt;
&lt;li&gt;It will expire in 30 days (we could control the expiration period with the &lt;code&gt;--expiry&lt;/code&gt; option but 30 days are enough for our purposes).&lt;/li&gt;
&lt;li&gt;It contains additional information (&lt;code&gt;role=admin&lt;/code&gt;) which we'll use later for authorization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can go to &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt;, copy-paste our token, and check if it really contains the info above. Just paste your JWT in the giant text field on the left. You'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4knwzos149cgzpyfect9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4knwzos149cgzpyfect9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you miss those "30 days"? They are encoded in the &lt;code&gt;exp&lt;/code&gt; property as a timestamp, and you surely can &lt;a href="https://www.unixtimestamp.com" rel="noopener noreferrer"&gt;convert&lt;/a&gt; the value back to a human-readable date. You can also check the signature by pasting your key into the "Verify Signature" text input and re-pasting your JWT.&lt;/p&gt;

&lt;p&gt;Now we're ready to fix the web application. Open the &lt;code&gt;dashboard-app/src/App.js&lt;/code&gt; file. After a few imports, you'll see the lines like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CUBEJS_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SOME_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cubejsApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cubejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CUBEJS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cubejs-api/v1`&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines configure the Cube.js &lt;a href="https://cube.dev/docs/frontend-introduction?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;client library&lt;/a&gt; to look for the API at &lt;code&gt;localhost:4000&lt;/code&gt; and pass a particular token. Change &lt;code&gt;SOME_TOKEN&lt;/code&gt; to the JWT you've just generated and verified, then stop the web application (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), and run it again with &lt;code&gt;npm start&lt;/code&gt;. We'll see that the web application works again and passes the JWT that we've just added to the API with the &lt;code&gt;Authorization&lt;/code&gt; header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2rpntn8xd9elql16jf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2rpntn8xd9elql16jf3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To double-check, we can run the same query with the same header in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:4000/cubejs-api/v1/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTUxNjUwNjAsImV4cCI6MTYxNzc1NzA2MH0.BNC8xlkB8vmuT0T6s1a5cZ3jXwhcHrAVNod8Th_Wzqw'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'query={"measures": ["Orders.count"], "dimensions": ["Orders.status"]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.data'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to check that if you remove the header or change just a single symbol of the token, the API returns an error, and never then result.&lt;/p&gt;

&lt;p&gt;‼️ &lt;strong&gt;We were able to add authentication and secure the API with JSON Web Tokens.&lt;/strong&gt; Now the API returns the result only if a valid JWT is passed. To generate such a JWT, one should know the key which is currently stored in the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Now, as we're becalmed, it's time to proceed to the next step and add authorization 🤿&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2. Authorization with JWTs
&lt;/h1&gt;

&lt;p&gt;As we already know, the essence of authorization is letting users perform certain actions based on who they are. How do we achieve that?&lt;/p&gt;

&lt;p&gt;We can make decisions about actions that users are permitted to perform based on the additional information (or &lt;em&gt;claims&lt;/em&gt;) in their JWTs. Do you remember that, while generating the JWT, we've supplied the payload of &lt;code&gt;role=admin&lt;/code&gt;? We're going to make the API use that payload to permit or restrict users' actions. &lt;/p&gt;

&lt;p&gt;Cube.js allows you to access the payload of JWTs through the &lt;a href="https://cube.dev/docs/security/context?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;security context&lt;/a&gt;. You can use the security context to modify the &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;data schema&lt;/a&gt; or support &lt;a href="https://cube.dev/docs/multitenancy-setup?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;multi-tenancy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, let's update the data schema.&lt;/strong&gt; In the &lt;code&gt;schema/Orders.js&lt;/code&gt; file, you can find the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This SQL statement says that any query to this cube operates with all rows in the &lt;code&gt;public.orders&lt;/code&gt; table. Let's say that we want to change it as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"admin" users can access all data&lt;/li&gt;
&lt;li&gt;"non-admin" users can access only a subset of all data, e.g., just 10 %&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve that, let's update the &lt;code&gt;schema/Orders.js&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.orders &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SECURITY_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafeValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WHERE id % 10 = FLOOR(RANDOM() * 10)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here? Let's break it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SECURITY_CONTEXT.role&lt;/code&gt; allows us to access the value of the "role" field of the payload. With &lt;code&gt;SECURITY_CONTEXT.role.unsafeValue()&lt;/code&gt; we can directly use the value in the JavaScript code and modify the SQL statement. In this snippet, we check that the role isn't equal to the "admin" value, meaning that a "non-admin" user sent a query.&lt;/li&gt;
&lt;li&gt;In this case, we're appending a new &lt;code&gt;WHERE&lt;/code&gt; SQL statement where we compare the value of &lt;code&gt;id % 10&lt;/code&gt; (which is the remainder of the numeric id of the row divided by 10) and the value of &lt;code&gt;FLOOR(RANDOM() * 10)&lt;/code&gt; (which is a pseudo-random number in the range of &lt;code&gt;0..9&lt;/code&gt;). Effectively, it means that a "non-admin" user will be able to query a 1/10 of all data, and as the value returned by &lt;code&gt;RANDOM()&lt;/code&gt; changes, the subset will change as well.&lt;/li&gt;
&lt;li&gt;You can also directly check the values in the payload against columns in the table with &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;requiredFilter&lt;/code&gt;. See data schema &lt;a href="https://cube.dev/docs/cube?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#context-variables-security-context" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Second, let's check how the updated schema restricts certain actions.&lt;/strong&gt; Guess what will happen if you update the schema, stop Cube.js (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), run Cube.js again with &lt;code&gt;npm run dev&lt;/code&gt;, then reload our web application.&lt;/p&gt;

&lt;p&gt;Right, nothing! 🙀 We're still using the JWT with &lt;code&gt;role=admin&lt;/code&gt; as the payload, so we can access all the data. So, how to test that the updated data schema works?&lt;/p&gt;

&lt;p&gt;Let's generate a new token without the payload or with another role with &lt;code&gt;npx cubejs-cli token --secret="NEW_SECRET" --payload="role=foobar"&lt;/code&gt;, update the &lt;code&gt;dashboard-app/src/App.js&lt;/code&gt; file, and reload our web application once again. Wow, now it's something... certainly less than before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lzf3algridqw3wurhdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lzf3algridqw3wurhdh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, let's check the same via the console.&lt;/strong&gt; As before, we can run the following command with an updated JWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:4000/cubejs-api/v1/load &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiZm9vYmFyIiwiaWF0IjoxNjE1MTk0MTIwLCJleHAiOjE2MTUxOTc3NjEsImp0aSI6ImMxYTk2NTY1LTUzNzEtNDNlOS05MDg0LTk0NWY3ZTI3ZDJlZSJ9.FSdEweetjeT9GJsqRqEebHLtoa5dVkIgWX4T03Y7Azg'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'query={"measures": ["Orders.count"], "dimensions": ["Orders.status"]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.data'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works like a charm:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5dbxy79i268dxdcbwsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5dbxy79i268dxdcbwsk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube.js also provides convenient extension points to use security context for &lt;a href="https://cube.dev/docs/multitenancy-setup?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;multi-tenancy support&lt;/a&gt;. In the most frequent scenario, you'll use the &lt;code&gt;queryTransformer&lt;/code&gt; to add mandatory &lt;a href="https://cube.dev/docs/multitenancy-setup?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#same-db-instance-with-per-tenant-row-level-security" rel="noopener noreferrer"&gt;tenant-aware filters&lt;/a&gt; to every query. However, you also can switch databases, their schemas, and cache configuration based on the security context.&lt;/p&gt;

&lt;p&gt;‼️ &lt;strong&gt;We were able to add authorization and use JWT claims to control the access to data.&lt;/strong&gt; Now the API is aware of users' roles. However, right now the only JWT is hardcoded into the web application and shared between all users.&lt;/p&gt;

&lt;p&gt;To automate the way JWTs are issued for each user, we'll need to use an external authentication provider. Let's proceed to the next step and add identification 🤿&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3. Identification via Auth0
&lt;/h1&gt;

&lt;p&gt;As we already know, the essence of identification is asking users who they are. An external authentication provider can take care of this, allowing users to authenticate via various means (e.g., their Google accounts or social profiles) and providing complementary infrastructure and libraries to integrate with your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; is a leading identity management platform for developers, &lt;a href="https://techcrunch.com/2021/03/04/making-sense-of-the-6-5b-okta-auth0-deal/" rel="noopener noreferrer"&gt;recently acquired&lt;/a&gt; by Okta, an even larger identity management platform. It securely stores all sensitive user data, has a convenient web admin panel, and provides front-end libraries for various frameworks. We'll use Auth0's integration with React but it's worth noting that Auth0 has integrations with all major front-end frameworks, just like Cube.js. &lt;/p&gt;

&lt;p&gt;On top of that, Auth0 provides many advanced features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User roles — you can have admins, users, etc.&lt;/li&gt;
&lt;li&gt;Scopes — you can set special permissions per user or per role, e.g, to allow some users to change your app’s settings or perform particular Cube.js queries.&lt;/li&gt;
&lt;li&gt;Mailing — you can connect third-party systems, like SendGrid, to send emails: reset passwords, welcome, etc.&lt;/li&gt;
&lt;li&gt;Management — you can invite users, change their data, remove or block them, etc.&lt;/li&gt;
&lt;li&gt;Invites — you can allow users to log in only via invite emails sent from Auth0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auth0 allows you to implement an industry-standard &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth 2.0 flow&lt;/a&gt; with ease. OAuth 2.0 is a proven protocol for external authentication. In principle, it works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our application redirects an unauthenticated user to an external authentication provider.&lt;/li&gt;
&lt;li&gt;The provider asks the user for its identity, verifies it, generates additional information (JWT included), and redirects the user back to our application.&lt;/li&gt;
&lt;li&gt;Our application assumes that the user is now authenticated and uses their information. In our case, the user's JWT can be sent further to Cube.js API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, now it's time to use Auth0 to perform identification and issue different JWTs for each user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, let's set up an Auth0 account.&lt;/strong&gt; You'll need to go to &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; website and sign up for a new account. After that, navigate to the "&lt;a href="https://manage.auth0.com/dashboard/us/dev-vubjtv0z/applications" rel="noopener noreferrer"&gt;Applications&lt;/a&gt;" page of the admin panel. To create an application matching the one we're developing, click the "+ Create Application" button, select "Single Page Web Applications". Done!&lt;/p&gt;

&lt;p&gt;Proceed to the "Settings" tab and take note of the following fields: "Domain", "Client ID", and "Client Secret". We'll need their values later.&lt;/p&gt;

&lt;p&gt;Then scroll down to the "Allowed Callback URLs" field and add the following URL as its value: &lt;code&gt;http://localhost:3000&lt;/code&gt;. Auth0 requires this URL as an additional security measure to make sure that users will be redirected to our very application.&lt;/p&gt;

&lt;p&gt;"Save Changes" at the very bottom, and proceed to the "&lt;a href="https://manage.auth0.com/dashboard/us/dev-vubjtv0z/rules" rel="noopener noreferrer"&gt;Rules&lt;/a&gt;" page of the admin panel. There, we'll need to create a rule to assign "roles" to users. Click the "+ Create Rule" button, choose an "Empty rule", and paste this script, and "Save Changes":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cube.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule will check the domain in users' emails, and if that domain is equal to "cube.dev", the user will get the admin role. You can specify your company's domain or any other condition, e.g., &lt;code&gt;user.email === 'YOUR_EMAIL'&lt;/code&gt; to assign the admin role only to yourself.&lt;/p&gt;

&lt;p&gt;The last thing here will be to register a new Auth0 API. To do so, navigate to the "&lt;a href="https://manage.auth0.com/dashboard/us/dev-vubjtv0z/apis" rel="noopener noreferrer"&gt;APIs&lt;/a&gt;" page, click "+ Create API", enter any name and &lt;code&gt;cubejs&lt;/code&gt; as the "Identifier" (later we'll refer to this value as "audience").&lt;/p&gt;

&lt;p&gt;That's all, now we're done with the Auth0 setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, let's update the web application.&lt;/strong&gt; We'll need to add the integration with Auth0, use redirects, and consume the information after users are redirected back.&lt;/p&gt;

&lt;p&gt;We'll need to add a few configuration options to the &lt;code&gt;dashboard-app/.env&lt;/code&gt; file. Note that two values should be taken from our application's settings in the admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;REACT_APP_AUTH0_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cubejs&lt;/span&gt;
&lt;span class="py"&gt;REACT_APP_AUTH0_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;VALUE_OF_DOMAIN_FROM_AUTH0&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;REACT_APP_AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;VALUE_OF_CLIENT_ID_FROM_AUTH0&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we'll need to add Auth0 React library to the &lt;code&gt;dashboard-app&lt;/code&gt; with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @auth0/auth0-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we'll need to wrap the React app with &lt;code&gt;Auth0Provider&lt;/code&gt;, a companion component that provides Auth0 configuration to all React components down the tree. Update your &lt;code&gt;dashboard-app/src/index.js&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  import React from 'react';
  import ReactDOM from 'react-dom';
  import { HashRouter as Router, Route } from 'react-router-dom';
  import ExplorePage from './pages/ExplorePage';
  import DashboardPage from './pages/DashboardPage';
  import App from './App';
&lt;span class="gi"&gt;+ import { Auth0Provider } from "@auth0/auth0-react";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  ReactDOM.render(
&lt;span class="gi"&gt;+   &amp;lt;Auth0Provider
+     audience={process.env.REACT_APP_AUTH0_AUDIENCE}
+     domain={process.env.REACT_APP_AUTH0_DOMAIN}
+     clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
+     scope={'openid profile email'}
+     redirectUri={process.env.REACT_APP_AUTH0_REDIRECT_URI || window.location.origin}
+     onRedirectCallback={() =&amp;gt; {}}
+   &amp;gt;
&lt;/span&gt;      &amp;lt;Router&amp;gt;
        &amp;lt;App&amp;gt;
          &amp;lt;Route key="index" exact path="/" component={DashboardPage} /&amp;gt;
          &amp;lt;Route key="explore" path="/explore" component={ExplorePage} /&amp;gt;
        &amp;lt;/App&amp;gt;
      &amp;lt;/Router&amp;gt;
&lt;span class="gi"&gt;+   &amp;lt;/Auth0Provider&amp;gt;,
&lt;/span&gt;  document.getElementById('root'));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last change will be applied to the &lt;code&gt;dashboard-app/src/App.js&lt;/code&gt; file where the Cube.js client library is instantiated. We'll update the &lt;code&gt;App&lt;/code&gt; component to interact with Auth0 and re-instantiate the client library with appropriate JWTs when Auth0 returns them.&lt;/p&gt;

&lt;p&gt;First, remove these lines from &lt;code&gt;dashboard-app/src/App.js&lt;/code&gt;, we don't need them anymore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- const API_URL = "http://localhost:4000";
- const CUBEJS_TOKEN = "&amp;lt;OLD_JWT&amp;gt;";
- const cubejsApi = cubejs(CUBEJS_TOKEN, {
-   apiUrl: `${API_URL}/cubejs-api/v1`
- });
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, add the import of an Auth0 React hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ import { useAuth0 } from '@auth0/auth0-react';
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, update the &lt;code&gt;App&lt;/code&gt; functional component to match these code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCubejsApi&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Get all Auth0 data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth0&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Force to work only for logged in users&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Redirect not logged users&lt;/span&gt;
      &lt;span class="nf"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Get Cube.js instance with accessToken&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initCubejs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAccessTokenSilently&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_AUTH0_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openid profile email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;setCubejsApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cubejs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:4000/cubejs-api/v1`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;getAccessTokenSilently&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Init Cube.js instance with accessToken&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cubejsApi&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;initCubejs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initCubejs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Show indicator while loading&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CubeProvider&lt;/span&gt; &lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ApolloProvider&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppLayout&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/AppLayout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ApolloProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CubeProvider&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! Now, you can stop the web application (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), and run it again with &lt;code&gt;npm start&lt;/code&gt;. You'll be redirected to Auth0 and invited to log in. Use any method you prefer (e.g., Google) and get back to your app. Here's what you'll see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwuc6nq1kjbvai518yky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwuc6nq1kjbvai518yky.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It appears that our application receives a JWT from Auth0, sends it to the API, and fails with "Invalid token". Why is that? Surely, because the API knows nothing about our decision to identify users and issue JWT via Auth0. We'll fix it now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, let's configure Cube.js to use Auth0.&lt;/strong&gt; Cube.js provides convenient built-in integrations with &lt;a href="https://cube.dev/docs/security/jwt/auth0?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; and &lt;a href="https://cube.dev/docs/security/jwt/aws-cognito?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics" rel="noopener noreferrer"&gt;Cognito&lt;/a&gt; that can be configured solely through the &lt;code&gt;.env&lt;/code&gt; file. Add these options to this file, substituting &lt;code&gt;&amp;lt;VALUE_OF_DOMAIN_FROM_AUTH0&amp;gt;&lt;/code&gt; with an appropriate value from above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;CUBEJS_JWK_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://&amp;lt;VALUE_OF_DOMAIN_FROM_AUTH0&amp;gt;/.well-known/jwks.json&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_JWT_ISSUER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://&amp;lt;VALUE_OF_DOMAIN_FROM_AUTH0&amp;gt;/&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_JWT_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cubejs&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_JWT_ALGS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_JWT_CLAIMS_NAMESPACE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, save the updated &lt;code&gt;.env&lt;/code&gt; file, stop Cube.js (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), and run Cube.js again with &lt;code&gt;npm run dev&lt;/code&gt;. Now, if you refresh the web application, you should see the result from the API back, the full dataset or just 10 % of it depending on your user and the rule you've set up earlier:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtj7semow5g95mtpsnb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtj7semow5g95mtpsnb6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;‼️ &lt;strong&gt;We were able to integrate the web application and the API based on Cube.js with Auth0 as an external authentication provider.&lt;/strong&gt; Auth0 identifies all users and generates JWTs for them. Now only logged-in users are able to access the app and perform queries to Cube.js. Huge success!&lt;/p&gt;

&lt;p&gt;The only question remains: once we have users with different roles interacting with the API, how to make sure we can review their actions in the future? Let's see what Cube.js can offer 🤿&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4. Accountability with audit logs
&lt;/h1&gt;

&lt;p&gt;As we know, the essence of accountability is being able to understand what actions were performed by different users.&lt;/p&gt;

&lt;p&gt;Usually, logs are used for that purpose. When and where to write the logs? Obviously, we should do that for every (critical) access to the data. Cube.js provides the &lt;a href="https://cube.dev/docs/config?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=multi-tenant-analytics#options-reference-query-transformer" rel="noopener noreferrer"&gt;queryTransformer&lt;/a&gt;, a great extension point for that purpose. The code in the &lt;code&gt;queryTransformer&lt;/code&gt; runs for every query &lt;em&gt;before it's processed&lt;/em&gt;. It means that you can not only write logs but also modify the queries, e.g., add filters and implement multi-tenant access control.&lt;/p&gt;

&lt;p&gt;To write logs for every query, update the &lt;code&gt;cube.js&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cube.js configuration options: https://cube.dev/docs/config&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;queryTransformer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;securityContext&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with role &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; executed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, stop Cube.js (by pressing &lt;code&gt;CTRL+C&lt;/code&gt;), run it again with &lt;code&gt;npm run dev&lt;/code&gt;, and refresh the web application. In the console, you'll see the output like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s5jw768n8jg2lqovlvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s5jw768n8jg2lqovlvg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Surely you can use a more sophisticated logger, e.g., a cloud-based logging solution such as &lt;a href="https://www.datadoghq.com" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;‼️ &lt;strong&gt;With minimal changes, we were able to add accountability to our app via a convenient Cube.js extension point.&lt;/strong&gt; Moreover, now we have everything from IAAA implemented in our app: identification, authentication, authorization, accountability. JSON Web Tokens are generated and passed to the API, role-based access control is implemented, and an external authentication provider controls how users sign in. With all these, multi-tenancy is only one line of code away and can be implemented in minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that's all, friends!&lt;/strong&gt; 🤿 I hope you liked this guide 🤗&lt;/p&gt;

&lt;p&gt;Here are just a few things you can do in the end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;go to the &lt;a href="https://github.com/cube-js/cube.js/" rel="noopener noreferrer"&gt;Cube.js repo&lt;/a&gt; on GitHub and give it a star ⭐️&lt;/li&gt;
&lt;li&gt;share a link to this guide on Twitter, Reddit, or with a friend 🙋‍♀️&lt;/li&gt;
&lt;li&gt;share your insights, feedback, and what you've learned about security, IAAA, Auth0, and Cube.js in the comments below ↓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P.S. I'd like to thank Aphyr for the &lt;a href="https://youtu.be/eSaFVX4izsQ?t=20" rel="noopener noreferrer"&gt;inspiration&lt;/a&gt; for the fake "George Orwell" quote at the beginning of this guide.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
