Custom Users

The Custom Users connection is a User Provider that allows you to implement a set of webhook callbacks that UserHub uses to keep the UserHub user object in sync with your database.

There are two basic webhook actions you need to implement:

  • List users: action is used to import users and keep users up-to-date in bulk. This is called when first setup and periodically to ensure all users exist in UserHub. By default the mostly-recently-created users should be returned first.
  • Get a user: action is used to get an individual user during Portal sign-in and as part of the background sync process to ensure the user hasn't been deleted (if absent from list).

Setup Webhook

First set up a webhook using one the supported SDKs.

Implement Methods

Next you'll need to implement the list and get methods in your code.

userdao.ts
import { connectionsv1, WebhookUserNotFound } from "@userhub/sdk";

class User {
  public id: number;
  public firstName: string;
  public lastName: string;
  public email: string;

  constructor({
    id,
    firstName,
    lastName,
    email,
  }: {
    id: number;
    firstName?: string;
    lastName?: string;
    email?: string;
  }) {
    this.id = id;
    this.firstName = firstName || "";
    this.lastName = lastName || "";
    this.email = email || "";
  }

  public toCustomUser(): connectionsv1.CustomUser {
    return {
      id: this.id.toString(),
      displayName: (this.firstName + " " + this.lastName).trim(),
      email: this.email,
      emailVerified: true,
      disabled: false,
    };
  }
}

export class UserDAO {
  private users: User[];

  constructor() {
    this.users = [
      new User({
        id: 1,
        firstName: "Donna",
        lastName: "Beaufort",
        email: "[email protected]",
      }),
      new User({
        id: 2,
        firstName: "Richard",
        lastName: "Carroll",
        email: "[email protected]",
      }),
      new User({
        id: 3,
        firstName: "Mike",
        email: "[email protected]",
      }),
    ];

    // sort by newest first
    this.users.sort((a: User, b: User): number => {
      return a.id > b.id ? -1 : 1;
    });
  }

  public async list(
    input: connectionsv1.ListCustomUsersRequest,
  ): Promise<connectionsv1.ListCustomUsersResponse> {
    const res: connectionsv1.ListCustomUsersResponse = { users: [] };

    // page size is the max, you can return less
    const pageSize = Math.min(input.pageSize, 2);

    const start = Number(input.pageToken || "0");
    if (isNaN(start)) {
      console.log(`failed to parse page token: ${input.pageToken}`);

      return res;
    }

    const end = Math.min(start + pageSize, this.users.length);

    for (const user of this.users.slice(start, end)) {
      res.users.push(user.toCustomUser());
    }

    if (this.users.length > end) {
      res.nextPageToken = end.toString();
    }

    return res;
  }

  public async get(
    input: connectionsv1.GetCustomUserRequest,
  ): Promise<connectionsv1.CustomUser> {
    const id = Number(input.id);

    for (const user of this.users) {
      if (user.id === id) {
        return user.toCustomUser();
      }
    }

    throw new WebhookUserNotFound();
  }
}

Optional: Implement Tests

Ideally you'll implement tests for your implementation.

userdao.test.ts
import assert from "node:assert";
import test from "node:test";

import { WebhookUserNotFound } from "@userhub/sdk";

import { UserDAO } from "./userdao";

test("UserDAO.list", async () => {
  const userDAO = new UserDAO();

  let res = await userDAO.list({ pageSize: 100 });
  assert.equal(res.users.length, 2);
  assert.equal(res.users[0].id, "3");
  assert.equal(res.users[1].id, "2");
  assert.equal(res.nextPageToken, "2");

  res = await userDAO.list({ pageSize: 100, pageToken: res.nextPageToken });
  assert.equal(res.users.length, 1);
  assert.equal(res.users[0].id, "1");
  assert.equal(res.nextPageToken, undefined);
});

test("UserDAO.get", async () => {
  const userDAO = new UserDAO();

  let user = await userDAO.get({ id: "1" });
  assert.equal(user.id, "1");
  assert.equal(user.displayName, "Donna Beaufort");
  assert.equal(user.email, "[email protected]");

  await assert.rejects(userDAO.get({ id: "20" }), WebhookUserNotFound);
});

Register Actions

Next you'll to update the webhook handler to call your new methods.

main.ts
import { UserDAO } from "./userdao";

// ...

const wh = new Webhook(signingSecret);

const userDAO = new UserDAO();
wh.onListUsers(userDAO.list.bind(userDAO));
wh.onGetUser(userDAO.get.bind(userDAO));

// ...

Create Connection

The final step is to create the Custom Users connection.

  1. Ensure the webhook is running and publicly routable
  2. Go to the Admin Console and click Connections via the Developers dropdown or Tenant settings
  3. Click the Setup button on Custom Users
  4. Select the desired webhook and click Save
  5. If the connection is marked as Active, you should be able to navigate to Users and see the imported users
PreviousAuth0
NextFirebase Auth

Turn users intorevenue
$

Subscribe to monthly product updates

© 2024 UserHub

Integrations

    UserHub & Auth0UserHub & Stripe BillingUserHub & Google CloudUserHub & FirebaseUserHub & Custom Auth