Can I use this?

Webiny Enterprise license is required to use this feature.

This feature is available since Webiny v5.34.0.

What you’ll learn
  • how to integrate Auth0 with a multi-tenant project

Overview
anchor

Auth0 integration replaces the default Cognito setup, and allows companies to manage users and their access to Webiny instances from within Auth0. Since Webiny’s system of permissions contains rich permission objects, you can’t define these in Auth0. Instead, assignment to security groups is happening using JSON Web Token (JWT) claims, which help you map every identity to a specific security group within Webiny, on a particular tenant.

Auth0 Sign In WidgetAuth0 Sign In Widget
(click to enlarge)

1) Prepare the Project
anchor

Your project needs to be at version 5.34.0, or greater, to use this feature. Please follow the upgrade guide to upgrade your project to the appropriate version.

Alternatively, you can create a new >=5.34.0 project, by running:

npx create-webiny-project my-project
MULTI-TENANCY
If you need to use multi-tenancy, follow this guide before continuing with the Auth0 setup.
IMPORTANT

If creating a new project, before following further instructions, make sure you complete the initial setup wizard with Cognito. This is a required step to successfully replace Cognito with Auth0.

2) Add New Dependencies
anchor

We need to add several new packages to the project.

Add Auth0 module dependency to the GraphQL API dependencies:

yarn workspace api-graphql add @webiny/api-security-auth0

Add Auth0 module dependency to the Headless CMS API dependencies:

yarn workspace api-headless-cms add @webiny/api-security-auth0

Add Auth0 module dependency to the Admin app dependencies:

yarn workspace admin add @webiny/app-admin-auth0

3) Configure Auth0 in the GraphQL API
anchor

We need to update security configuration in 2 Lambda functions: graphql and headlessCMS.

The difference between your original file and the one below, is that we removed all Cognito plugins, and added Auth0 plugin instead, with some other tweaks to the security configuration. The most important changes are highlighted and commented for your convenience.

IMPORTANT
You need to update two files, one for the `graphql` Lambda function and one for the `headlessCMS` Lambda function. Please grab the corresponding code from their dedicated tabs below. The code is similar, but _not_ the same.
apps/api/graphql/src/security.ts
import { DocumentClient } from "aws-sdk/clients/dynamodb";import { createTenancyContext, createTenancyGraphQL } from "@webiny/api-tenancy";import { createStorageOperations as tenancyStorageOperations } from "@webiny/api-tenancy-so-ddb";import { createSecurityContext, createSecurityGraphQL } from "@webiny/api-security";import { createStorageOperations as securityStorageOperations } from "@webiny/api-security-so-ddb";import { authenticateUsingHttpHeader } from "@webiny/api-security/plugins/authenticateUsingHttpHeader";import apiKeyAuthentication from "@webiny/api-security/plugins/apiKeyAuthentication";import apiKeyAuthorization from "@webiny/api-security/plugins/apiKeyAuthorization";import anonymousAuthorization from "@webiny/api-security/plugins/anonymousAuthorization";import { createAuth0 } from "@webiny/api-security-auth0";
export default ({ documentClient }: { documentClient: DocumentClient }) => [  /**   * Create Tenancy app in the `context`.   */  createTenancyContext({      storageOperations: tenancyStorageOperations({ documentClient })  }),
  /**   * Expose tenancy GraphQL schema.   */  createTenancyGraphQL(),
  /**   * Create Security app in the `context`.   */  createSecurityContext({      /**      * For Auth0, this must be set to `false`, as we don't have links in the database.      */      verifyIdentityToTenantLink: false,      storageOperations: securityStorageOperations({ documentClient })  }),
  /**   * Expose security GraphQL schema.   */  createSecurityGraphQL({      /**       * For Auth0, we must provide custom logic to determine the "default" tenant for current identity.       * Since we're not linking identities to tenants via DB records, we can just return the current tenant.       */      async getDefaultTenant(context) {          return context.tenancy.getCurrentTenant();      }  }),
  /**   * Perform authentication using the common "Authorization" HTTP header.   * This will fetch the value of the header, and execute the authentication process.   */  authenticateUsingHttpHeader(),
  /**   * Configure Auth0 authentication and authorization.   */  createAuth0({      /**       * `domain` is required for token verification.       */      domain: String(process.env.AUTH0_DOMAIN),      /**       * Construct the identity object and map token claims to arbitrary identity properties.       */      getIdentity({ token }) {          return {              id: token["sub"],              type: "admin",              displayName: token["name"],              // Assign any custom values you might need.              group: token["group"]          };      },      /**       * Get the slug of a security group to fetch permissions from.       */      getGroupSlug(context) {          const identity = context.security.getIdentity();
          // Return group slug you want to map this identity to.          return identity["group"];      }  }),
  /**   * API Key authenticator.   * API Keys are a standalone entity, and are not connected to users in any way.   * They identify a project, a 3rd party client, not a particular user.   * They are used for programmatic API access, CMS data import/export, etc.   */  apiKeyAuthentication({ identityType: "api-key" }),
  /**   * Authorization plugin to fetch permissions for a verified API key.   * The "identityType" must match the authentication plugin used to load the identity.   */  apiKeyAuthorization({ identityType: "api-key" }),
  /**   * Authorization plugin to load permissions for anonymous requests.   * This allows you to control which API resources can be accessed publicly.   * The authorization is performed by loading permissions from the "anonymous" user group.   */  anonymousAuthorization()];

The last thing to do is to define Auth0 environment variables.

In your project directory, open the .env file, and define the following variables. To find the values of these variables, in your Auth0 dashboard, navigate to Applications -> Applications, and open the desired application. The values you need will be located under the Basic Information section.

# Auth0 variables for the API.
AUTH0_DOMAIN=https://dev-12345678.us.auth0.com
AUTH0_CLIENT_ID=123456781234567812345678

# Auth0 variables for React apps (webpack will pick this up automatically, due to the REACT_APP_ prefix naming convention).
REACT_APP_AUTH0_DOMAIN=https://dev-12345678.us.auth0.com
REACT_APP_AUTH0_CLIENT_ID=123456781234567812345678

This will ensure that, when Pulumi starts the deploy process, these environment variables are present in process.env, and are assigned to your Lambda functions.

4) Configure Admin App
anchor

Open apps/admin/src/App.tsx and replace the Cognito plugin with Auth0 plugin:

apps/admin/src/App.tsx
import React from "react";import { Admin } from "@webiny/app-serverless-cms";import { Auth0 } from "@webiny/app-admin-auth0";import "./App.scss";
export const App = () => {return (  <Admin>    <Auth0        auth0={{            domain: String(process.env.REACT_APP_AUTH0_DOMAIN),            clientId: String(process.env.REACT_APP_AUTH0_CLIENT_ID)        }}        rootAppClientId={String(process.env.REACT_APP_AUTH0_CLIENT_ID)}    />  </Admin>);};

With this, you’re ready to deploy the project. To deploy the whole project, run the following:

yarn webiny deploy --env=dev

Or deploy just the api and admin by running:

yarn webiny deploy apps/api --env=dev && yarn webiny deploy apps/admin --env=dev

5) Configuring App Client ID for New Tenants
anchor

Once your project is deployed, open your Admin app. If everything went well, you should be presented with an Auth0 login screen. The Auth0 user you’re using to login must have access to the root tenant app client, and have the appropriate claims assigned to the user’s JWT so the user can be mapped to the correct security group, as described in the Configure Auth0 in the GraphQL API section.
Recommendation

We recommend that your main root tenant admin user has a full-access group assignment. Think of this user as a super admin user who can run system setup, create new tenants, and so on. A full-access security group is always present in the system. So, you don’t need to create it manually.

Once you’ve logged in, navigate to Tenant Manager -> Tenants. In the tenant creation form, there is a dedicated input for Auth0 App Client ID:

Assign App Client ID to TenantAssign App Client ID to Tenant
(click to enlarge)

The value entered here is what the Admin app will be fetching via the API during app bootstrap (on every browser reload). Since multiple tenants can use the same app client ID, for development purposes, you can have 1 Auth0 app shared between all the developers in the team, across all tenants in their development environments.

6) Accessing Admin App of a Particular Tenant
anchor

To point the browser to a specific tenant you want to access, you simply need to use the tenantId query string parameter:

http://localhost:3001/?tenantId=root
http://localhost:3001/?tenantId=61e6db7813ab8e0009e252d1
...