How to create a custom administrative UI using Management API

I am trying to create a simple custom administrative UI for adding, updating, and deactivating users using the Management API. Additionally, the users administrated will need to have two custom attributes: company which comes from my application’s database, and user_type, which is initially “user” or “administrator”.

PROBLEM 1: Listing All Users
I was able to list all users using the Management API by calling getUsers and paging through results. But, according to the limitations in the documentation (Retrieve Users with the Get Users Endpoint), there is no endpoint for retrieving an immediately consistent list of all users in real-time. I could call get user by email or get user by id if I had the email addresses or ids of my users. But, I don’t know how to get these.

PROBLEM 2: Creating New Users
The createUser code below returned: statusCode: 400, error: ‘Bad Request’, message: “Payload validation error: ‘Missing required property: connection’.”, errorCode: ‘invalid_body’. See the sample code below.

//-----------------
app.get(“/createUser”, async (req, res) => {
var newUser = {
“email”: “john.doe@gmail.com”,
“phone_number”: “+199999999999999”,
“user_metadata”: {},
“blocked”: false,
“email_verified”: false,
“phone_verified”: false,
“app_metadata”: {},
“given_name”: “John”,
“family_name”: “Doe”,
“name”: “John Doe”,
“nickname”: “Johnny”,
“picture”: “//secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png”,
“user_id”: “abc”,
“connection”:“Username-Password-Authentication”,
“password”: “secret”,
“verify_email”: false,
“username”: “johndoe”
};

var options = {
  method: 'POST',
  url: `https://${domain}/api/v2/users`,
  params: {
   body: newUser
  },
  headers: {authorization: `Bearer ${mgmtApiAccessToken}`}
};

return axios.request(options).then(function (response) {
    res.send(response.data);
}).catch(function (error) {
  console.error(error);
});

}); ///createUser

//----------------

So, if it is too difficult to build a custom administrative UI, is there a way I can just lock down the Auth0 dashboard so that some dashboard users can only add, update, and delete users but also provide company and user_type? I see that, if I upgrade, I can create tenant members who only have the ability to view and edit users. Though, this does not fulfill my need to have them easily add companies and user_types…

Hi @idatarbel,

  • Problem 1: listing all users

You’re right, you either need to use the Bulk User Export and some sort of Middleware / DB indexing, or use the GET call to /api/v2/users without queries and some custom work. You will have to use a payload to be able to paginate results (otherwise you’d be limited to 50) or to have control on what you query. You can also use the “q” param, which corresponds to the same queries as when you search for users in the dashboard, so you can test it graphically. I realise it’s not “real-time” but with an adapted pagination it can be very quick. You could also split users alphabetically and/or even index users in your own middleware / DB / service if you wanted to speed up your custom UI. I think the best option would depend on your use case and the amount of users you expect. My personal favourite would be using the get_users call with criteria that would ensure less than 1000 results (sort by first letter of email address for example) along with a middleware depending on the amount of results I’d expect.

  • Problem 2: creating new users

I suppose this is simple matter of fixing your payload. I don’t see what’s wrong your example, I’ve just tested the below based on our docs and it works (Python):

		payload = {
			'name': name,
			'email': email,
			'connection': 'Username-Password-Authentication',
			'password': password
		}

You could try checking your browser’s dev tools while creating a user and use a similar payload?

Thank you so much for the reply!

Regarding Problem 1:
You are suggesting either using export or /api/v2/users. But, isn’t /api/v2/users not immediately consistent? So, if I get a list of users from it, couldn’t that list be incomplete if users have been added?

Regarding Problem 2:
I tried your suggestion of pulling the payload from the browser.
Here’s the payload from Google Developer Console:

{
  "email": "test6@verisave.com",
  "password": "2PissMeOff!1",
  "connection": "Username-Password-Authentication"
}

Here’s the error I get:

 data: {
      statusCode: 400,
      error: 'Bad Request',
      message: "Payload validation error: 'Missing required property: connection'.",
      errorCode: 'invalid_body'
    }

Here is the entire code for my little test application:

const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const helmet = require("helmet");
const { auth } = require("express-oauth2-jwt-bearer");
const authConfig = require("./auth_config.json"); //Fix
const app = express();
let apiPort = 667;
let frontendPort = 666;
const port = process.env.API_PORT || apiPort;
const appPort = process.env.SERVER_PORT || frontendPort;
const appOrigin = authConfig.appOrigin || `http://localhost:${appPort}`;
if (
  !authConfig.domain ||
  !authConfig.audience ||
  authConfig.audience === "YOUR_API_IDENTIFIER"
) {
  console.log(
    "Exiting: Please make sure that auth_config.json is in place and populated with valid domain and audience values"
  );

  process.exit();
}
app.use(morgan("dev"));
app.use(helmet());
app.use(cors({ origin: appOrigin }));

var axios = require("axios").default;
let audience = authConfig.audience;
let clientId = authConfig.clientId;
let domain = authConfig.domain;
let clientSecret = authConfig.clientSecret;
let mgmtApiAccessToken = "";

function getMgmtApiAccessToken(){
    var options = {
        method: 'POST',
        url: `https://${domain}/oauth/token`,
        headers: {'content-type': 'application/x-www-form-urlencoded'},
        data: new URLSearchParams({
          grant_type: 'client_credentials',
          client_id: clientId,
          client_secret: clientSecret,
          audience: audience
        })
      };
      
      axios.request(options).then(async function (response) {
        mgmtApiAccessToken = await response.data.access_token;
        console.log("mgmtApiAccessToken ==> " + mgmtApiAccessToken)

        return response.data.access_token;
      }).catch(function (error) {
        console.error(error);
      });
}


app.get("/", async (req, res) => {
    getMgmtApiAccessToken();
    res.send({
        msg: "Success",
      });  
});


function createUser(){

}

app.get("/createUser", async (req, res) => {
    var newUser ={
        "email": "test6@verisave.com",
        "password": "2PissMeOff!1",
        "connection": "Username-Password-Authentication"
      };

    var options = {
      method: 'POST',
      url: `https://${domain}/api/v2/users`,
      params: {
       body: newUser
      },
      headers: {authorization: `Bearer ${mgmtApiAccessToken}`}
    };
    
    return axios.request(options).then(function (response) {
        res.send(response.data);
    }).catch(function (error) {
      console.error(error);
    });
}); ///createUser



/**
 * getNextPage
 * 
 * @param {*} pageNumber 
 * @param {*} recordsPerPage 
 * @returns 
 */
function getNextPage(pageNumber, recordsPerPage){
    var options = {
      method: 'GET',
      url: `https://${domain}/api/v2/users`,
      params: {
        q: 'email:*',
        page: pageNumber,
        per_page: recordsPerPage,
        include_totals: 'true',
        search_engine: 'v3',
        sort: "created_at:1"
      },
      headers: {authorization: `Bearer ${mgmtApiAccessToken}`}
    };
    
    return axios.request(options).then(function (response) {
        return new Promise((resolve, reject) => {
            resolve(response.data);
            reject("ERROR")
        });
    }).catch(function (error) {
      console.error(error);
    });
}

function getUserCount(){
    var options = {
        method: 'GET',
        url: `https://${domain}/api/v2/users`,
        params: {
          q: 'email:*',
          page: '0',
          per_page: '2',
          include_totals: 'true',
          search_engine: 'v3',
          sort: "created_at:1"
        },
        headers: {authorization: `Bearer ${mgmtApiAccessToken}`}
      };
    
    return axios.request(options).then(function (response) {
        return new Promise((resolve, reject) => {
            resolve(response.data.total);
            reject("ERROR")
        });
    }).catch(function (error) {
        console.error(error);
    });
}

app.get("/getUsers", async (req, res) => {

    getUserCount().then(
        async (totalRecords) => {
            console.log("userCount: " + totalRecords);
            var recordsPerPage = 50;
            var totalPages = Math.ceil(totalRecords /recordsPerPage);
            var allResults = new Array();
            var thisPage ;
            console.log("totalPages: " + totalPages)
            for (let pageNumber =0; pageNumber < totalPages; pageNumber++){
                thisPage = await getNextPage(pageNumber, recordsPerPage);
                allResults.push(thisPage.users);
            }//for
 
            res.send(allResults);
        }
    );
}); ///getUsers

getMgmtApiAccessToken();
app.listen(port, () => console.log(`API Server listening on port ${port}`));

I still need a solution to problem 1.

I resolved problem 2 on my own by testing the API call in Postman and then generating code in Postman. The code in the online doc doesn’t work. Here’s working code:

  var newUser ={
        "email": "test9@example.com",
        "password": "2P27aifav0f!!--!?",
        "connection": "Username-Password-Authentication"
      };

    var options = {
      method: 'POST',
      url: `https://${domain}/api/v2/users`,
      data: newUser,
      headers: {authorization: `Bearer ${mgmtApiAccessToken}`}
    };
    
    return axios.request(options).then(function (response) {
        res.send(response.data);
    }).catch(function (error) {
      console.error(error);
    });