Skip to content
Udibo
On this page
View Markdown

Build a client portal

Availability: this walkthrough describes a service template available through repository access arranged with Udibo. It is not a public standalone starter. You can follow the same design in your own application using the first-login guide. For runnable public package examples, use starter projects.

The template is a React Router application served by Hono, with its projects, documents and sessions in Postgres through drizzle. It needs Node.js 26, pnpm and a Postgres database.

What the walkthrough covers

One person can read and revise the entire workspace. Others see only one project through their organization, one directly shared document, or nothing. The portal reports sharing permission but does not yet write grants.

Before you start

You need an accessible Udibo Identity tenant, a development application configured as a first-party, confidential authorization-code client with refresh tokens, and a client secret. Choose opaque access tokens for these walkthroughs; they validate through introspection. See application registration for the exact settings.

The app requests openid profile email. Define application permissions in your tenant's permission catalog and assign roles separately; permission names are not additional OAuth scopes.

Set up the example

Register http://localhost:8009/auth/callback and configure:

VariableValue
UDIBO_ISSUERYour tenant's issuer origin, https://<tenant-id>.udibo.com
APP_ORIGINhttp://localhost:8009; the registered callback is {APP_ORIGIN}/auth/callback
UDIBO_CLIENT_IDThe registered application's client ID
UDIBO_CLIENT_SECRETA secret created for that application
DATABASE_URLThe Postgres database the projects, documents and sessions live in
SESSION_SECRETAt least 32 bytes of entropy that encrypts sessions, sign-ins in progress and page cursors; new,old rotates it
PORTOptional. Where the server listens; 8009 by default

Every required value is checked at startup, and a missing one stops the server with its name. Keep secrets on the server.

SESSION_SECRET is required. It encrypts the sessions the app stores in its database, the browser cookie that holds a sign-in in progress, and the cursors the document list pages with. Generate one with node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))". It accepts a comma-separated list, and every entry needs at least 32 bytes. The first entry encrypts new sessions, the sign-in cookie and the cursors, and keys the document list's order; the entries after it only open sessions created before a rotation. To rotate without signing everyone out, set SESSION_SECRET=<new>,<old> and drop the old entry after 14 days, the session lifetime. A sign-in started before the rotation and finished after it fails, and the person signs in again; a half-read document list starts again from its first page.

In your copy of the template, install, migrate the database and start it. pnpm db:seed is optional and adds a sample workspace of two projects and their documents:

Shell
pnpm install --frozen-lockfile
cp .env.example .env    # then fill it in
pnpm db:migrate
pnpm db:seed
pnpm dev                # http://localhost:8009

Define documents:read, documents:write, and documents:share in your permission catalog, and roles carrying them. Register the document and project resource types. Place resource grants in your tenant's dashboard under Access control → Resources, or from your own backend with an application credential (see What sharing can do today). Permissions explains the resource and grant API shapes.

1. Ask about the document being accessed

A token cannot enumerate every document a person may access. The application gets candidate documents from its own data store and asks Udibo about those specific resources.

The list uses POST /api/check/batch: one set of checks for documents and another for their projects, chunked to the endpoint's limit of 100 candidates. It returns only documents allowed by the resulting decisions.

A direct read checks the same access boundary. Updating a document additionally requires documents:write. Keep the read and write decisions separate.

Checkpoint: a user granted one document sees that document and cannot read another by guessing its ID.

2. Model inheritance deliberately

Udibo does not store your application's document-to-project hierarchy. The portal knows that relationship, checks both resources, and accepts an appropriate grant on either.

A project grant therefore reaches its documents because the application applies that rule. It does not automatically reach arbitrary descendants in Udibo.

Checkpoint: grant a project and verify access to its documents, then confirm a different project's documents remain inaccessible.

3. Share with people or organizations

A resource grant can name a person or an organization. An organization grant resolves through accepted membership and can restrict which member roles may exercise it.

The active organization on a token is not a substitute for a resource check. An organization-scoped documents:read role does not by itself grant access to every document resource. Conversely, a person with a direct resource grant may have no permissions claim on their token and still be allowed to read it.

Checkpoint: test a direct recipient, an organization recipient, a member outside the grant's role restriction, and someone with a similarly named organization permission but no resource grant.

What sharing can do today

The example reads documents:share to decide who should be offered a sharing option. That application permission does not authorize a write to Udibo's tenant-management API.

The example writes no resource grants itself. Your application can: on your tenant's host, POST /api/resource-grants accepts your application's machine credential once a tenant administrator assigns it resource_grants.write and its token carries the identity:organizations:write scope. See permissions for placing a grant. Machine tokens still do not provide general tenant-management access.

Replace the example's workspace tables with your own data, preserve consistent responses for missing and inaccessible documents, and use the production checklist before launch.

Last verified 2026-10-13.