Connect Auth0 tenants

Problem statement

We need to have our user’s data stored in different regions, and for that, the only option is to create different tenants, that at the same time should be connected to the same application. How can we accomplish this?

Solution

There are 2 options to connect Auth0 tenants.

The ideal option is to set an enterprise connection between both tenants, like an OIDC connection.

If the above is not possible, the alternative option is to create a custom DB on tenant A, and using ROPG to connect to the custom DB of tenant B.

Custom DB login script for tenant A:

async function login(email, password, callback) {
  const axios = require("axios");
  const jwt_decode = require("jwt-decode");
  const querystring = require("querystring");
  const tokenEndpoint = "https://<TENANT_B>/oauth/token""; // Tenant B token endpoint
  const clientId = "<TENANT_B_CLIENT_ID>"; // Tenant B clientID

  async function passwordGrant() {
    //Password grant request to tenant B
    const res = await axios.post(
      tokenEndpoint,
      querystring.stringify({
        grant_type: "password",
        client_id: clientId,
        username: email,
        password: password,
        scope: "openid profile email",
      }),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      }
    );

    return res.data;
  }

  function decodeToken(token) {
    const decodedToken = jwt_decode(token);
    const user = {
      user_id: decodedToken.sub,
      email: decodedToken.email,
      username: decodedToken.name,
      name: decodedToken.name,
      email_verified: decodedToken.email_verified,
      picture: decodedToken.picture,
      nickname: decodedToken.nickname,
    };
    return user;
  }

  try {
    const authResponse = await passwordGrant();
    var user = decodeToken(authResponse.id_token);
    return callback(null, user);
  } catch (err) {
    return callback(
      new WrongUsernameOrPasswordError(email, "Wrong username or password :(")
    );
  }
}

Custom DB get user script for tenant A:

async function getByEmail(email, callback) {
  const axios = require("axios");
  const querystring = require("querystring");
  const jwt_decode = require("jwt-decode");
  const tokenEndpoint = "https://<TENANT_B>/oauth/token""; // Tenant B token endpoint
  const getUserEndpoint =
    "https://<TENANT_B>/api/v2/users-by-email?email="" + email;
  const API2clientId = "<TENANT_B_CLIENT_ID>";
  const API2clientSecret = "<TENANT_B_CLIENT_SECRET>";
  const aud = "https://<TENANT_B>/api/v2/"";

  async function getAccesToken() {
    //Password grant request to tenant B
    const res = await axios.post(
      tokenEndpoint,
      querystring.stringify({
        grant_type: "client_credentials",
        client_id: API2clientId,
        client_secret: API2clientSecret,
        audience: aud,
      }),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      }
    );

    return res.data.access_token;
  }

  async function getToken() {
    let token = "";
    if (global.externalDBToken) {
      token = global.externalDBToken;
      const decodedToken = jwt_decode(token);
      var dateNow = new Date();
      if (decodedToken.exp < dateNow.getTime() / 1000) {
        token = await getAccesToken();
        global.externalDBToken = token;
      }
    } else {
      token = await getAccesToken();
      global.externalDBToken = token;
    }
    return token;
  }

  async function getUser() {
    const token = await getToken();
    const res = await axios({
      method: "GET",
      url: getUserEndpoint,
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    return res.data;
  }

  function decodeToken(user) {
    const newUser = {
      user_id: user.user_id,
      email: user.email,
      name: user.name,
      email_verified: user.email_verified,
      nickname: user.nickname,
      picture: user.picture,
    };
    return newUser;
  }

  try {
    const authResponse = await getUser();
    if (authResponse.length === 0) {
      return callback(null);
    }
    const user = decodeToken(authResponse[0]);
    return callback(null, user);
  } catch (err) {
    return callback(new Error("Could not determine if user exists or not :("));
  }
}