---
title: "Full app embedding quickstart"
description: "Embed the whole Metabase app in your own app with JWT single sign-on, with each customer’s access scoped to their own data."
url: "https://www.metabase.com/docs/latest/embedding/full-app-embedding-quick-start-guide"
version: "65"
---

# Full app embedding quickstart

> If you are just starting out with Metabase embedding, consider using [Modular embedding](https://www.metabase.com/docs/latest/embedding/modular-embedding.md) - an improved, more customizable option for embedding Metabase components.

You'll embed the full Metabase application in your app. Once logged in, people can view a Metabase dashboard in your web app, and be able to use the full Metabase application to explore their data, and only their data.

## Prerequisites

> Full app embedding is only available on [Pro](https://store.metabase.com/checkout/embedding) and [Enterprise](https://www.metabase.com/product/enterprise) plans (both self-hosted and on Metabase Cloud).

- You have an app that you can embed Metabase in.
- You have a Pro or Enterprise subscription of Metabase. If you're unsure where to start, sign up for a free trial for [Pro On-Prem](https://store.metabase.com/checkout/embedding). If you have Docker Desktop installed, you can just search for "metabase-enterprise" to find the Docker image and run it. Alternatively, you can follow [these instructions](https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-docker.md#pro-or-enterprise-quick-start).

The code featured in this guide can be found in our [sample repo](https://github.com/metabase/metabase-nodejs-express-interactive-embedding-sample).

## Set up SSO and full app embedding in Metabase

### Have a dashboard ready to embed

You'll first need a dashboard to embed. If you don't have one yet, you can use the Example Dashboard Metabase includes on new instances or you can generate one using x-rays.

Visit that dashboard and make a note of its URL, e.g. `/dashboard/1-e-commerce-insights`. You'll need to put this relative URL in your app, as you'll use the dashboard as the first page that logged-in people will see when they visit the analytics section in your app. It's enough to include the ID only and omit the rest of the URL, e.g. `/dashboard/1`.

You could also use the dashboard's [Entity ID](https://www.metabase.com/docs/latest/installation-and-operation/serialization.md#metabase-uses-entity-ids-to-identify-metabase-items). On the dashboard, click on the **info** button. On the **Overview** tab, look for the dashboard's **Entity ID**. Copy that Entity ID. You'll use that Entity ID in the iframe's `src` URL: (e.g., `src=/dashboard/entity/[Entity ID]`).

### Enable full app embedding

In Metabase, click the **grid** icon in the upper right, select **Embedding hub**, and go to **Security**. Toggle on **Full-app embedding**.

Under **Authorized origins for full-app embedding** (it appears once you flip the toggle), add the URL of the website or web app where you want to embed Metabase. If you're running your app locally, you can add localhost and specify the port number, e.g. `http://localhost:8080`.

#### SameSite configuration

If you're embedding Metabase in a different domain, you may need to [set the session cookie's SameSite value to `none`](https://www.metabase.com/docs/latest/embedding/full-app-embedding.md#embedding-metabase-in-a-different-domain).

### Set up SSO with JWT in your Metabase

#### Enable authentication with JWT

In the Embedding hub sidebar, click **Authentication**. The page opens to the JWT settings.

#### Set JWT identity provider URI

In your app, you'll create a route for SSO at `/sso/metabase`. In the **JWT Identity Provider URI** field, enter the URL of your SSO route. For example, our sample app runs on port 8080, so in that case this **JWT Identity Provider URI** could be `http://localhost:8080/sso/metabase`.

#### Generate a JWT signing key

Click on the **Set up key** button to generate a signing key. Keep this key a secret. You'll use this JWT signing key on your server. If you generate another key, you'll overwrite the existing key, so you'll need to update the key in your app as well.

Copy this key, as you'll need it in the next section.

### Save and enable JWT authentication

We'll set up group synchronization later, but for now, be sure to click the **Save and enable** button to activate JWT authentication.

## Set up SSO with JWT in your app's server

### Add the signing key and Metabase site URL to your app

Here you'll need to input some values for your SSO to work.

You'll want to declare up to two constants in your app:

- `METABASE_JWT_SHARED_SECRET`, paste the JWT signing key that you got from your Metabase here.
- `METABASE_SITE_URL`, which points to your Metabase instance's root path.

```javascript
const METABASE_JWT_SHARED_SECRET = "YOURSIGNINGKEY";
const METABASE_SITE_URL = "https://your-domain.metabaseapp.com";
```

The signing key should preferably be setup as an environment variable, to avoid accidentally committing your key to your app's repo.

### Add a JWT library to your app's server

Add a JWT library to your app. For example, if you're using a Node backend with JavaScript, we recommend using [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken).

In your terminal:

```sh
npm install jsonwebtoken --save
```

And in your app, require the library:

```javascript
import jwt from "jsonwebtoken";
```

### Restricting access to certain routes

Presumably, your app already has some way of making sure some routes are only accessible after having signed in. Our examples use a simple helper function named `restrict` that protects these routes:

```javascript
function restrict(req, res, next) {
  if (req.session.user) {
    next();
  } else {
    req.session.returnTo = req.originalUrl;
    req.session.error = "Access denied!";
    res.redirect("/login");
  }
}
```

### Add a function to sign users

We need to write a function to sign user JWTs, using the JWT library.

```javascript
const signUserToken = (user) =>
  jwt.sign(
    {
      email: user.email,
      first_name: user.firstName,
      last_name: user.lastName,
      exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
    },
    METABASE_JWT_SHARED_SECRET,
  );
```

### Add a `sso/metabase` route

You'll need to add a route to sign people in to your Metabase via SSO using JWT. If the person isn't signed in to your app yet, your app should redirect them through your sign-in flow. In the code below, this check and redirection is handled by the `restrict` function we introduced earlier.

```javascript
app.get("/sso/metabase", restrict, (req, res) => {
  const ssoUrl = new URL("/auth/sso", METABASE_INSTANCE_URL);
  ssoUrl.searchParams.set("jwt", signUserToken(req.session.user));
  ssoUrl.searchParams.set("return_to", req.query.return_to?.toString() ?? "/");

  res.redirect(ssoUrl.href);
});
```

Metabase creates an account for first-time sign-ins.

### CHECKPOINT: sign in to your Metabase using SSO

Make sure you are signed out of your Metabase. From the Metabase sign-in page, click on "Sign in with SSO". You should be redirected to your app.

Log in to your app. Your app should redirect you to your Metabase welcome page. If the person doesn't yet have a Metabase account, Metabase should create an account for them.

## Embed Metabase in your app

Now to embed your Metabase in your app. You'll want to set up a route to serve your embedded analytics. Let's call it `/analytics`. Note that we're using the `restrict` helper function (defined above) because this page should only be viewable after people sign in to your app.

In this route, we need to render an iframe that will load your Metabase. The `src` attribute of the iframe should point to the relative path of the SSO endpoint of your app. Once the person signs in to your app (and therefore in to your Metabase), we add the query string parameter `return_to` so that the iframe displays the requested dashboard.

`METABASE_DASHBOARD_PATH` should be pointing to the relative path of the dashboard you created at the beginning of this guide (`/dashboard/[ID]`, or if you used the dashboard's Entity ID: `/dashboard/entity/[Entity ID]`).

```javascript
app.get("/analytics", restrict, (req, res) => {
  const METABASE_DASHBOARD_PATH = "/dashboard/entity/[Entity ID]"; // e.g., `/dashboard/1` or `/dashboard/entity/nXg0q7VOZJp5a3_hceMRk`
  const iframeUrl = `/sso/metabase?return_to=${METABASE_DASHBOARD_PATH}`;

  res.send(
    `<iframe src="${iframeUrl}" frameborder="0" width="1280" height="600" allowtransparency></iframe>`,
  );
});
```

The `METABASE_DASHBOARD_PATH` is just the first thing people will see when they log in, but you could set that path to any Metabase URL. And since you're embedding the full Metabase, people will be able to drill through the data and view other questions, dashboards, and collections.

### CHECKPOINT: view a Metabase dashboard in your app

People using your app should now be able to access `/analytics` and view your embedded Metabase dashboard.

How to test: Sign in to your app and visit the `/analytics` route. You should see the Metabase dashboard.

> If you're using the Safari browser, and you're serving Metabase and your app from different domains, you may need to go to Safari's settings and turn off [Prevent cross-site tracking](https://support.apple.com/guide/safari/prevent-cross-site-tracking-sfri40732/mac).

## Set up a group in Metabase

Now that you have SSO and full app embedding set up, it's time to set up groups so that you can apply permissions to your embedded Metabase entities (questions, dashboards, collections, and so on).

### Add a `groups` key to your token

Recall the `signUserToken` function used to create the JWTs. Add a `groups` key to the signed token that maps to an array. Metabase will look at the values in that array to see if any of the values map to a group in Metabase (We'll walk through mapping groups in a bit).

```javascript
const signUserToken = (user) =>
  jwt.sign(
    {
      email: user.email,
      first_name: user.firstName,
      last_name: user.lastName,
      groups: ["Customer-Acme"],
      exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
    },
    METABASE_JWT_SHARED_SECRET,
  );
```

### Create a group in Metabase

In Metabase, click the **grid** icon and go to **Admin** > **People** > **Groups**. Click the **Create a group** button. Add a group that corresponds with a group in your app. If you're using the sample app, add a group called `Customer Acme`.

### Synchronize groups between Metabase and your app

You'll map this string in the `groups` key to a Metabase group, so that when the person signs in via SSO, Metabase automatically assigns them to the appropriate Metabase group.

Go to **Embedding hub** > **Authentication**.

In the **Group mapping** section, select **Automatic**. Metabase adds people to the Metabase groups whose names match the group names in their JWT. If your `groups` array and your Metabase group both use `Customer Acme`, Metabase maps them for you.

If your JWT group names and your Metabase group names don't match, select **Manual** instead:

1. Click **New mapping**.
2. In the **JWT group name** field, enter `Customer-Acme`.
3. From **Metabase groups**, select **Customer Acme**.
4. Click **Add mapping**.

Metabase saves each mapping as soon as you add it.

![Mapping user attributes to groups.](https://www.metabase.com/docs/latest/embedding/images/sync-groups.png)

### CHECKPOINT: verify that Metabase assigns people to groups when they log in

First, sign out of Metabase and sign in using SSO.

Then sign out and sign in to your Metabase as an admin and go to **Admin** > **People** section and verify that Metabase added the person to the appropriate group.

Note: only Metabase admins and group managers are aware of groups. Basic users have no concept of groups, and no way of knowing which groups they're a part of.

## Set permissions

Now to apply permissions to that group so that people only see data specific to their accounts.

### Reset permissions for the All Users group

Metabase ships with two initial groups: "Admins" and "All Users". By default, Metabase gives the "All Users" group access to connected data sources. And since Metabase grants people the privileges of their most permissive group, you'll want to restrict what the "All Users" groups can see before you add them to groups with limited or no access to data sources and collections.

To reset permissions for the All users group, click the **grid** icon and go to **Admin** > **Permissions**. Under the **Data** tab, go to **Groups** and select **All Users**. For the **Sample Database** in the **View data** column, select "Blocked". Click **Save changes** and a modal will pop up summarizing what you're changing. Click **Yes**.

![Resetting permissions of the All Users group to](https://www.metabase.com/docs/latest/embedding/images/all-users.png)

### Allow view access to the automatically generated dashboards collection

Still in the **Permissions** tab, click on the **Collections** sub-tab, then on the **Automatically generated dashboards** collection, and set the **Collection access** permissions for the **All Users** group to **View**.

Click **Save changes**, then **Yes**.

### Add a user attribute to the token

You can include user attributes in the JSON web token. Metabase will pick up any keys from the JWT payload and store them as user attributes. Among other use cases, you can use these user attributes to set row-level permissions on tables, so people can only see results tied to their accounts.

If you're using our sample app, edit the `signUserToken` function used to create the JWT by adding a key `account_id` with value `28`.

```javascript
const signUserToken = (user) =>
  jwt.sign(
    {
      email: user.email,
      first_name: user.firstName,
      last_name: user.lastName,
      // hard-coded account ID added to this object
      // just to test sandboxing with Metabase's Sample Database: Invoices table
      account_id: 28,
      groups: ["Customer-Acme"],
      exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minute expiration
    },
    METABASE_JWT_SHARED_SECRET,
  );
```

That user ID will correspond to the `Account ID` column in the Sample Database's Invoices table. We'll use this `account_id` user attribute for row and column security on the Invoices table, so people will only see rows in that table that contain their account ID.

Note that to persist the user attribute in Metabase, you'll need to log in. Log in to your app as a non-admin, and visit the page with your embedded Metabase.

### Set row-level permissions

In Metabase, go to **Admin** > **Permissions**. Under the **Data** tab on the left, click on a group. For "Sample Database", change its **Data access** column to **Granular**.

Metabase will display a list of the tables in the database. Next, change **View data access** for the "Invoices" table to **Row and column security**.

![Adding row and column security to a table.](https://www.metabase.com/docs/latest/embedding/images/secured-invoices-table.png)

Next, Metabase will prompt you with a modal to associate a column in that table with a user attribute.

Leave the **Filter by a column in a table** option checked, and associate the "Account ID" column in the Invoices table with the user attribute `account_id`. (Note that Metabase will only display the user attributes if the user has signed in through SSO before.)

Click **Save** to confirm your select. Then click the **Save changes** button in the upper right.

Metabase will ask if you're sure you want to do this. You _are_ sure.

### CHECKPOINT: view secured dashboard

Make sure you've logged out of your previous session.

Log in to your app, navigate to `/analytics`. The dashboard will now present different information, since only a subset of the data is visible to this person. Click on **Browse Data** at the bottom of the left nav. View your secured **Invoices** table, and you should only see rows in that table that are associated with the person's account.

## Hiding Metabase elements

You can decide to [show or hide various Metabase elements](https://www.metabase.com/docs/latest/embedding/full-app-embedding.md#showing-or-hiding-metabase-ui-components), like whether to show the nav bar, search or the **+New** button, and so on.

For example, to hide the logo and the top navigation bar of your embedded Metabase, you'd append the query string parameters `?logo=false&top_nav=false` to the `return_to` URL that you include in the SSO redirect.

In the handler of your `/sso/metabase` path, add the query parameters:

```javascript
ssoUrl.searchParams.set(
  "return_to",
  `${req.query.return_to ?? "/"}?logo=false&top_nav=false`,
);
```

### CHECKPOINT: verify hidden UI elements

Sign out and sign in to your app again and navigate to `/analytics`. Your embedded Metabase should not include the logo or the top navigation.

## Next steps

You can [customize how Metabase looks](https://www.metabase.com/docs/latest/configuring-metabase/appearance.md) in your app: fonts, colors, and logos.
