# 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](https://www.udibo.com/docs/identity/get-started). For runnable public package examples, use [starter projects](https://www.udibo.com/docs/identity/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](https://www.udibo.com/docs/identity/applications) 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: | Variable | Value | | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `UDIBO_ISSUER` | Your tenant's issuer origin, `https://.udibo.com` | | `APP_ORIGIN` | `http://localhost:8009`; the registered callback is `{APP_ORIGIN}/auth/callback` | | `UDIBO_CLIENT_ID` | The registered application's client ID | | `UDIBO_CLIENT_SECRET` | A secret created for that application | | `DATABASE_URL` | The Postgres database the projects, documents and sessions live in | | `SESSION_SECRET` | At least 32 bytes of entropy that encrypts sessions, sign-ins in progress and page cursors; `new,old` rotates it | | `PORT` | Optional. 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=,` 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: ```bash 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](https://www.udibo.com/docs/identity/client-portal-template#what-sharing-can-do-today)). [Permissions](https://www.udibo.com/docs/identity/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](https://www.udibo.com/docs/identity/permissions#3-assign-a-role-to-someone) 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](https://www.udibo.com/docs/identity/production) before launch. _Last verified 2026-10-13._